Repository navigation
Task 8.2b: Android createReport through a one-slot registry - #20
Conversation
…through R8 Co-authored-by: Cursor <cursoragent@cursor.com>
…rom render
Banner: Bugsee Android SDK 7.3.0 [beb390dc0].
One handled payload (nonce 6877ef246252):
{"name":"TypeError","reason":"E2E handled 6877ef246252","frames":[{"data":{"member":"bugseeE2EThrowSite"},"debug_id":"8a1c2f4e-0d3b-5e6f-9a7b-6877ef246252"}],"cause":{"name":"RangeError","reason":"inner 6877ef246252"},"signature":"8362dd3b5785297d7d387925b94ce0ea5e857cfc","platform_os":"android","debug_ids":{"http://localhost:8081/index.bundle//&platform=android&dev=true&lazy=true&minify=false&app=com.bareexample&modulesOnly=false&runModule=true&excludeSource=true&sourcePaths=url-server":"8a1c2f4e-0d3b-5e6f-9a7b-6877ef246252"}}
Gated release R1–R3 bundles (nonce 824ac61adb58): exactly one crash,
com.bugsee.reactnative.ReactNativeWebException, reason JSON with
"E2E fatal 824ac61adb58". No JavascriptException Bugsee bundle observed
(R2 prediction unmet; assertion left intact for controller ruling).
Thread: exception handled sent tid→comm BugseeRN-except (TASK_COMM_LEN
truncation of BugseeRN-exceptions).
Co-authored-by: Cursor <cursoragent@cursor.com>
Controller ruling: R13's second Bugsee crash is not retained on Android SDK 7.3.0. Logcat still shows FATAL EXCEPTION / JavascriptException and the process dies; Bugsee keeps exactly one crash named ReactNativeWebException. Gated R2/R3 updated; debug case 10 stays it.failing. awaitBundles(1) for the release beforeAll. Release verify (WOD_LX1): R1–R3 PASS; one bundle 894ba5df-af93-4d4b-9678-5d06ec218db4.bundle.zip. Co-authored-by: Cursor <cursoragent@cursor.com>
ExceptionStage registers _bugseeDebugIds synchronously before mounting BugseeE2EThrower, so the map exists for the first render throw. Root polls for a crash with the boundary reason instead of requiring two bundles. Release R1 asserts exactly one ours. Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
…cannot be lost Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
beta3 0d9c9d0a-9 stores the unhandled report and does not claim override_report.plcrash on the next launch. The one-crash asserts stay it.failing so an empty recovery list cannot pass as the desired outcome. Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
…e stubbed Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
…ended Co-authored-by: Cursor <cursoragent@cursor.com>
There was a problem hiding this comment.
Stale comment
Deep code review — Task 8.2b (Android createReport registry)
Reviewed
0542501againstfeat/phase-8-2a(cd79431), with the JScreateReport/CreatedReportcontract,ReportHandlerBridge/WrapperEventBusreload pattern, and plan P5 (one outstanding created report per JS runtime, released onupload()and on moduleinvalidate).Android
createReportis no longer a stub: it reserves a process-wide slot, asksBugsee.createReport, and mintscr-<n>throughCreatedReports. The stamp added in the follow-up commit correctly stops a listener that outlivedclear()from filling or freeing a later reservation. That part is sound. The remaining hole is the other half of the same reload overlap this module already documents.Finding
Severity: P1 High
Location:BugseeModule.java:140(CreatedReports.shared().clear()), with no matchingattachin the constructor
Problem:CreatedReportsis a process-wide singleton, butinvalidate()clears it unconditionally. Every other process-wide sink in this class is identity-checked for the documented ordering where a reload constructs and attaches the new module before the old one is invalidated. Stamps only protectfulfil; they do not protect an already-reserved or already-fulfilled slot fromclear().
Impact: On RN context recreation the next runtime can lose a report it just created (E_REPORT_HANDLE_DEADonread/update/upload), or its firstcreateReport()can spuriously rejectE_REPORT_CREATE_BUSYbecause the dying runtime still occupies the only slot. Plan P5 (“released by a JS reload”) is supposed to free the old runtime’s slot, not the live one.
Scenario:
- Reload starts. New
BugseeModuleis constructed and new JS runs while the old module is still alive (invalidate()javadoc already states this order).- New JS calls
createReport(),tryReservesucceeds, the SDK listener fulfilscr-n.- Old
invalidate()runsCreatedReports.shared().clear()and drops that handle.- New JS
read()/update()/upload()all miss and rejectE_REPORT_HANDLE_DEAD. The filled report is gone.Symmetric window the other way: old JS still holds a reservation or a fulfilled report when new JS starts. New
createReport()hitstryReserve() == 0and rejectsE_REPORT_CREATE_BUSY. The dying runtime thenclear()s, leaving the slot empty. Typical app code does not retry BUSY, so a launch-timecreateReport()fails for that runtime.Identity-checking only
detachis not enough: if the new module has already taken ownership, an oldclear()must no-op, which means leftover state from the previous owner would otherwise sit in the slot forever (new JS does not knowcr-(n-1)and cannottakeit). The new module has to claim the registry on construct and drop that leftover then, before new JS runs.
Fix: MirrorWrapperEventBus/ReportHandlerBridge/DataRequestBridge:
CreatedReports.attach(owner)in the constructor: set the owner, drop any leftover reservation/report (new JS cannot use those handles), leave stamps invalid so an in-flight listener cannot fulfil into the new runtime.CreatedReports.detach(stale)frominvalidate():clear()only ifstaleis still the owner.- Unit-test both sides:
attach(B)thendetach(A)must not drop B’s reservation;attach(B)must drop A’s leftover so B cantryReserve.
1. Overall risk: Medium
One P1 on the reload overlap this module already treats as real. Not a crash, not a security issue, and the in-process one-slot + stamp path looks correct for a single live runtime.
2. Merge recommendation: Wait
Fix the owner-scoped attach/detach before merging. The stamp work should stay.
3. Most important issue to fix
Unconditional
CreatedReports.clear()oninvalidate(), with no constructorattach. That is the same class of bug the rest of this file already refuses to ship.4. Positive observations
- The stamp/
tryReservesplit is the right answer for a listener that outlivesclear(), andaStaleStampDoesNotConsumeANewerReservationpins both a stale report and a stale null fulfil.- Concurrent reserve admission, fresh
cr-<n>handles, and take-vs-get are covered.- Created-report ops correctly reuse
ReportOpsand copy files (move = false). Taking the handle beforeBugsee.uploadmatches the JSCreatedReportcontract that the handle is dead whether or not upload succeeds.E_REPORT_CREATE_BUSYmatchesReportErrorCode.CreateBusyand the JS translation path from 8.2a.Sent by Cursor Automation: Bugsee code review
Mutation was surviving the boundary at severity 5 and any rewrite of the TypeError and RangeError text, which pulled the score under the break threshold. Co-authored-by: Cursor <cursoragent@cursor.com>
The deadline is a third caller into that barrier. On a slow CI runner it fires while the two threads are parked and steals a signal, so the test never finishes. Co-authored-by: Cursor <cursoragent@cursor.com>
Those branches were untested, so their mutants survived and the mutation score stayed under the break threshold. Co-authored-by: Cursor <cursoragent@cursor.com>
There was a problem hiding this comment.
Stale comment
Deep code review — Task 8.2b (re-review of
4d61e8a)Re-reviewed against
feat/phase-8-2a(cd79431), the JScreateReport/CreatedReportcontract from 8.2a, plan P5, Android SDK 7.3.0Bugsee.createReport/createAndPrepareBugReport/openAsync, and the iOS sibling (#21).Production Android is unchanged since 0542501 (the stamp on
fulfil). The three later commits are tests only: JS mutation pins for severity/argument text, the iOS settle-once race no longer has a third deadline caller, andcreated-report.test.tscovers omitted mime types, empty path/name, and native rejection mapping.Prior finding
P1 — unconditional
CreatedReports.clear()oninvalidate(): not addressed (owner declined attach/detach).The 0.87 serial-teardown argument is accepted for that version: this constructor never reserves, so old
clear()cannot drop a reservation the new module has not opened yet, and the stamp still ignores a listener that outlivesinvalidate. The residual is the floor this package still declares (react-native >= 0.81). This method’s own javadoc still describes new-module-construct-before-old-invalidate forWrapperEventBus/ReportHandlerBridge. On that order, new JScreateReport()can reserve (or sit behind the dying runtime’s slot) before oldclear():
- new runtime already holds
cr-n→ oldclear()drops it →read/update/uploadrejectE_REPORT_HANDLE_DEAD;- dying runtime still occupies the one slot → new
createReport()rejectsE_REPORT_CREATE_BUSYonce, thenclear()frees an empty slot.Same residual as iOS #21. Stamps protect
fulfilonly; they do not protect an already-reserved or already-fulfilled slot fromclear().No new P0–P3
Walked
CreatedReports(reserve / fulfil / take / clear, stamp skip of 0, concurrent admission),BugseeModule.createReportand the fivecreatedReport*methods,ReportOpsreuse, and 7.3.0:
createReportalways invokes the listener (nullwhen not launched);openAsyncruns on the storage thread, so the native-modules thread is not blocked ononBeforeReportCreated.- Overlapping SDK creates are independent
ReportingRequestinstances (no iOS-style file-scope globals). Holding the slot until a stale listener returns would make reloadBUSY; ignoring a stale stamp is the right Android answer.takebeforeBugsee.upload(report, …)matches the JS contract that the handle is dead whether or not upload succeeds. The SDK looks the request up from theReport(WeakRef, then IRR on disk) and keeps a strong map fromopen()until discard, so a heldReportremains uploadable.addFileis copy (move = false). Attachment decline vs dead handle reuses the handler’s settle rule against this registry.JVM
CreatedReportsTestwas not re-run here (standalone Gradle lacked the Android plugin in this environment). CIandroidwas still pending at review time;rn compat0.81–0.87 had passed.
1. Overall risk: Low
One declined residual on 0.81 reload overlap. The in-process one-slot + stamp path is correct for a single live runtime.
2. Merge recommendation: Safe into
feat/phase-8-2aDo not treat owner-checked
clearas done for the 0.81 floor. iOS registry is Task 8.2c.3. Most important issue
The residual P1 above, if 0.81 (or any reload where new JS runs before old
invalidate) matters. No new production defect on this head.4. Positive observations
- Stamp/
tryReservestill correctly ignores a listener afterclear(), including a stale null fulfil, andaStaleStampDoesNotConsumeANewerReservationpins both.- Created-report ops correctly reuse
ReportOpsand copy files.E_REPORT_CREATE_BUSYmatchesReportErrorCode.CreateBusy.- The extra JS tests close the mutation holes that were surviving (severity 5, exact TypeError/RangeError text, omitted mime type, native rejection →
BugseeReportError).Sent by Cursor Automation: Bugsee code review
A shared go semaphore let the faster caller enter the next window and consume the other caller's release, so that caller never arrived and CI timed out. Co-authored-by: Cursor <cursoragent@cursor.com>
There was a problem hiding this comment.
Stale comment
Deep code review — Task 8.2b (re-review of
edc94cc)Delta since
4d61e8ais one test commit:testTwoConcurrentCallsSettleOncenow uses a separate enter/release semaphore pair per race window. A sharedgolet the faster caller leave window 0, enter window 1, and consume the other thread’s release, so that caller never reached window 1 (CI timed out). Per-window pairs plus a per-window 5s wait are the right fix. Production Android (CreatedReports,BugseeModule.createReport*) is unchanged since 0542501.Prior finding
P1 — unconditional
CreatedReports.clear()oninvalidate(): not addressed (owner declined attach/detach). Residual remains the 0.81 floor / any reload where new JScreateReportruns before oldinvalidate(HANDLE_DEADor a one-shotE_REPORT_CREATE_BUSY). Stamps still protectfulfilonly. Same decision as iOS #21.No new P0–P3
The settle-once barrier change does not alter
BGSRNSettleOnceproduction code._Thread_localphase is per GCD worker and is correct for a single run of this test (eachfire()visits the window twice).
1. Overall risk: Low
2. Merge recommendation: Safe into
feat/phase-8-2aDo not treat owner-checked
clearas done for the 0.81 floor. iOS registry is Task 8.2c.3. Most important issue
The residual P1 above. No new production defect on this head.
4. Positive observations
The per-window semaphore split matches how
RunRaceWindowis actually invoked (before the load, then after it) and removes a real CI timeout mode.Sent by Cursor Automation: Bugsee code review
There was a problem hiding this comment.
Stale comment
Deep code review — Task 8.2b (re-review of
edc94cc)Re-reviewed against
feat/phase-8-2a(cd79431), the JScreateReport/CreatedReportcontract from 8.2a, plan P5, Android SDK 7.3.0Bugsee.createReport/createAndPrepareBugReport/openAsync, and the iOS sibling (#21).Production Android is unchanged since 0542501 (the stamp on
fulfil). The only commit after the last review (4d61e8a) isedc94cc: test-only. Each settle-once race window now has its own enter/release pair so a faster GCD caller cannot leave window 0, enter window 1, and consume the other caller’s signal (the timeout that showed up as “caller 3”).Earlier follow-ups on this PR remain tests: JS mutation pins for severity/argument text, dropping the settle-once deadline as a third waiter, and
created-report.test.tscoverage for omitted mime types, empty path/name, and native rejection mapping.Prior finding
P1 — unconditional
CreatedReports.clear()oninvalidate(): not addressed (owner declined attach/detach).The 0.87 serial-teardown argument is accepted for that version: this constructor never reserves, so old
clear()cannot drop a reservation the new module has not opened yet, and the stamp still ignores a listener that outlivesinvalidate. The residual is the floor this package still declares (react-native >= 0.81). This method’s own javadoc still describes new-module-construct-before-old-invalidate forWrapperEventBus/ReportHandlerBridge. On that order, new JScreateReport()can reserve (or sit behind the dying runtime’s slot) before oldclear():
- new runtime already holds
cr-n→ oldclear()drops it →read/update/uploadrejectE_REPORT_HANDLE_DEAD;- dying runtime still occupies the one slot → new
createReport()rejectsE_REPORT_CREATE_BUSYonce, thenclear()frees an empty slot.Same residual as iOS #21. Stamps protect
fulfilonly; they do not protect an already-reserved or already-fulfilled slot fromclear().No new P0–P3
Walked
CreatedReports(reserve / fulfil / take / clear, stamp skip of 0, concurrent admission),BugseeModule.createReportand the fivecreatedReport*methods,ReportOpsreuse, and 7.3.0 bytecode (BugseeInternal.createReport,createAndPrepareBugReport,sendBugReport):
- A null listener returns without creating. A live listener always gets a callback: no app token → posted null on main; otherwise
openAsync.- Overlapping SDK creates are independent
ReportingRequests (no iOS-style file-scope globals). Holding the slot until a stale listener returns would make reloadBUSY; ignoring a stale stamp is the right Android answer.takebeforeBugsee.upload(report, …)matches the JS contract that the handle is dead whether or not upload succeeds.processAndUploadReportlooks the request up from the heldReportbefore that call returns.addFileis copy (move = false). Attachment decline vs dead handle reuses the handler’s settle rule against this registry.
CreatedReportsTestwas not re-run here. CI on 0542501 hadandroidandios unit (BugseeRNSupport)SUCCESS;lint, typecheck, unit, mutationFAILED (the hole the later JS tests close). CI onedc94ccwas still in progress at review time.
1. Overall risk: Low
One declined residual on 0.81 reload overlap. The in-process one-slot + stamp path is correct for a single live runtime.
2. Merge recommendation: Safe into
feat/phase-8-2aDo not treat owner-checked
clearas done for the 0.81 floor. iOS registry is Task 8.2c.3. Most important issue
The residual P1 above, if 0.81 (or any reload where new JS runs before old
invalidate) matters. No new production defect on this head.4. Positive observations
- Stamp/
tryReservestill correctly ignores a listener afterclear(), including a stale null fulfil, andaStaleStampDoesNotConsumeANewerReservationpins both.- Created-report ops correctly reuse
ReportOpsand copy files.E_REPORT_CREATE_BUSYmatchesReportErrorCode.CreateBusy.edc94ccis the right fix for the two-phase barrier: a sharedgocannot keep both callers in the same window. Per-window enter/release matches howBGSRNSettleOnceactually parks (before the load, then after it).Sent by Cursor Automation: Bugsee code review
Keep this branch's created-report registry. Keep main's split exception executors, the beta3 recovery comment, and omitted dialog fields as nil.
There was a problem hiding this comment.
Stale comment
Deep code review — Task 8.2b (re-review of
02ddf11)Reviewed the three-dot range
1fd9ded...02ddf11against the live tree, not the pre-mergeCreatedReportsdesign in isolation. The unique production work on this branch is still the one-slot registry (CreatedReports) and the fivecreatedReport*ops.02ddf11mergedmain(#19 stubs, splitExceptionExecutors, iOS comment/dialog nil) into that work.P0 — duplicate created-report methods after the merge
BugseeModulenow contains both the 8.2a stubs (resolvenull/E_REPORT_HANDLE_DEAD) and the 8.2b implementations. Six pairs share a signature. That is a Java compile error, not a runtime race: this head cannot build.The merge message said to keep this branch’s registry. The stub block at L771–L821 should have been deleted; it was not.
I did not wait for the in-flight Android CI job; duplicate methods fail
javacregardless.Prior finding
P1 — unconditional
CreatedReports.clear()oninvalidate(): still not addressed (owner declined attach/detach). Residual is the 0.81 floor / any reload where new JScreateReportruns before oldinvalidate(stolencr-n→HANDLE_DEAD, or a one-shotE_REPORT_CREATE_BUSY). Stamps still protectfulfilonly. Same decision as iOS #21. Do not treat it as newly introduced by this merge.No other new P1–P3 in
CreatedReports/ the realcreateReport*path. The stamp skip of 0, concurrent admission,takebeforeBugsee.upload,addFilecopy, and attachment settle-against-registry logic are unchanged since 0542501.
1. Overall risk: Critical
The merge does not compile. Registry correctness is moot until the stubs are removed.
2. Merge recommendation: Do not merge
Delete the 8.2a stub block, confirm
CreatedReportsTestandassembleDebug, then re-review. iOS registry remains Task 8.2c.3. Most important issues to fix
- Remove the six stub methods at
BugseeModule.javaL771–L821 so only the registry implementations remain.- Residual P1 (
clear()vs 0.81 reload overlap) — already declined; not a merge blocker once the class compiles.4. Positive observations
ExceptionExecutorssplit frommainis wired correctly (HANDLED/UNHANDLED); that part of the merge is fine.- The one-slot + stamp design is still the right Android answer for a late
onCreatedafterinvalidate.Sent by Cursor Automation: Bugsee code review
There was a problem hiding this comment.
Stale comment
Deep code review — Task 8.2b (re-review of
02ddf11)The branch was retargeted to
mainand merged1fd9ded(#19). Merge message: keep this branch's created-report registry; keep main's split exception executors, beta3 recovery comment, and omitted dialog fields as nil.
ExceptionExecutors(handled vs unhandled queues) and iOSshowReportDialogpassing omitted summary/description asnilare correctly taken from main. The registry itself (CreatedReports,deliverCreatedReport, stamp onfulfil) is still present.The merge did not drop the 8.2a stubs.
BugseeModule.javanow definescreateReportand all fivecreatedReport*methods twice.Findings
Severity: P0 Critical
Location:BugseeModule.java:771–821(stubs) and:830–973(registry implementations)
Problem: After merging main, the 8.2a stubs (promise.resolve(null)/rejectHandleDead) and this PR's real methods share the same signatures.javacrejects a class with twocreateReport(Promise)methods (and the same for the fivecreatedReport*ops).
Impact: The Android library does not compile. This slice cannot ship, and CIandroidcannot go green on this head.
Scenario: Merge of #19 onto this branch kept both sides of the conflict. Android compile:method createReport(Promise) is already defined in class BugseeModule(repeated for each duplicate).
Fix: Delete the six stub methods (the block commentedTask 8.2b. No registry yet). Keep the registry implementations. Also drop the unusedimport java.util.concurrent.Executorsleft afterEXCEPTION_EXECUTORmoved toExceptionExecutors.P1 residual (not addressed): unconditional
CreatedReports.clear()oninvalidate()(owner declined attach/detach). Same 0.81 reload overlap as before.
1. Overall risk: Critical
The merge does not compile. Nothing else on this head matters until the stubs are removed.
2. Merge recommendation: Wait
Delete the stubs, then this is again the 8.2b registry on top of main.
3. Most important issue to fix
Duplicate
createReport/createdReport*methods introduced by the main merge.4. Positive observations
- Split
ExceptionExecutorsfrom #17 is intact (unhandled is not queued behind handled).- iOS dialog omitted fields stay
nil(#18).- The registry, stamps, and
take-before-upload path were not dropped; they are just unreachable until the class compiles.Sent by Cursor Automation: Bugsee code review
…e the registry The merge kept both the 8.2a stubs and the registry methods, so the Android compile saw each method twice. Co-authored-by: Cursor <cursoragent@cursor.com>
There was a problem hiding this comment.
Stale comment
Deep code review — Task 8.2b (re-review of
5eb1935)
5eb1935deletes the six 8.2a stub methods the main merge left beside the registry.BugseeModulenow has onecreateReportand one of eachcreatedReport*op, all going throughCreatedReports. That was the compile break; it is gone.Prior findings
P0 — duplicate stubs + registry methods: fully addressed. Stub block (former L771–L821) is deleted. Confirmed a single definition of each method on this head.
P1 — unconditional
CreatedReports.clear()oninvalidate(): not addressed (owner declined attach/detach). Residual remains the 0.81 floor / any reload where new JScreateReportruns before oldinvalidate(HANDLE_DEADor a one-shotE_REPORT_CREATE_BUSY). Stamps still protectfulfilonly. Same decision as iOS #21.No new P0–P3
Registry, stamp skip of 0,
takebeforeBugsee.upload, file copy (move = false), and attachment settle-against-registry are unchanged since 0542501. SplitExceptionExecutorsfrom main is still wired. Leftover unusedimport java.util.concurrent.Executorsis not a compile error; not raised as a finding.
1. Overall risk: Low
The merge compiles again. One declined residual on 0.81 reload overlap.
2. Merge recommendation: Safe into
mainDo not treat owner-checked
clearas done for the 0.81 floor. iOS registry is Task 8.2c.3. Most important issue
The residual P1 above. No remaining production defect that blocks this slice.
4. Positive observations
The stub deletion matches the merge intent (“keep this branch’s created-report registry”) and is the minimum fix that makes
javacsucceed.Sent by Cursor Automation: Bugsee code review
There was a problem hiding this comment.
Deep code review — Task 8.2b (re-review of 5eb1935)
Reviewed 1fd9ded...5eb1935 against the live tree, the JS createReport / CreatedReport contract from 8.2a, plan P5, ReportHandlerBridge / WrapperEventBus reload identity-checks, and the iOS sibling (#21).
5eb1935 deletes the six 8.2a stub methods the main merge left beside the registry. BugseeModule now has a single createReport and a single copy of each createdReport* op, all going through CreatedReports. That was the compile break; it is gone.
Prior findings
P0 — duplicate stubs + registry methods: fully addressed. The stub block (former L771–L821, commented Task 8.2b. No registry yet) is deleted. Confirmed one definition each of createReport (L778), createdReportRead (L830), createdReportUpdate (L844), createdReportAddDataAttachment (L865), createdReportAddFileAttachment (L894), and createdReportUpload (L921).
P1 — unconditional CreatedReports.clear() on invalidate() (L125): not addressed (owner declined attach/detach). Residual remains the package floor (react-native >= 0.81) / any reload where new JS createReport runs before old invalidate:
- new runtime already holds
cr-n→ oldclear()drops it →read/update/uploadrejectE_REPORT_HANDLE_DEAD; - or the dying runtime still occupies the one slot → new
createReport()rejectsE_REPORT_CREATE_BUSYonce, thenclear()frees an empty slot.
Stamps still protect fulfil only. The 0.87 serial-teardown argument is accepted for that version (constructor never reserves). Same decision as iOS #21.
No new P0–P3
Walked CreatedReports (reserve / fulfil / take / clear, stamp skip of 0, concurrent admission), BugseeModule.createReport + the five ops, ReportOps reuse, and the JS wrapper:
tryReservethenBugsee.createReport; a throw ends that stamp; the listener carries the stamp so a lateonCreatedcannot fill or free a later reservation.takebeforeBugsee.uploadmatches the JS contract that the handle is dead whether or not upload succeeds. Android requests are independent (no iOS-style file-scope globals), so freeing the slot atupload()start is the right Android answer.addFileis copy (move = false). Attachment decline vs dead handle reuses the handler settle rule against this registry.E_REPORT_CREATE_BUSYmatchesReportErrorCode.CreateBusyand the 8.2a translation path.- Split
ExceptionExecutorsfrom main is still wired (HANDLED/UNHANDLED). - Leftover unused
import java.util.concurrent.Executorsis not a compile error; not raised as a finding.
Did not run CreatedReportsTest here: standalone Gradle failed with SDK location not found (ANDROID_HOME unset). CI android on this SHA was still queued at review time.
1. Overall risk: Low
The merge compiles again. One declined residual on 0.81 reload overlap. The in-process one-slot + stamp path is correct for a single live runtime.
2. Merge recommendation: Safe into main
Do not treat owner-checked clear as done for the 0.81 floor. iOS registry remains Task 8.2c.
3. Most important issue
The residual P1 above. No remaining production defect that blocks this slice.
4. Positive observations
- Stub deletion matches the merge intent (“keep this branch’s created-report registry”) and is the minimum fix that makes
javacsucceed. - Stamp/
tryReservestill correctly ignores a listener afterclear(), including a stale null fulfil, andaStaleStampDoesNotConsumeANewerReservationpins both. - Created-report ops correctly reuse
ReportOpsand copy files.
Sent by Cursor Automation: Bugsee code review


Summary
createReportkeeps one outstanding report. A second call isE_REPORT_CREATE_BUSY.fulfilwrites only when the reservation stamp captured attryReserveis still current, so a listener that outlivesinvalidatecannot fill the next runtime's slot.invalidatestill clears the registry.Test plan
CreatedReportsTest8/8, includingaStaleStampDoesNotConsumeANewerReservationassembleDebugMade with Cursor