Repository navigation
test(e2e): recover an Android native crash without a JS handler - #15
Conversation
Co-authored-by: Cursor <cursoragent@cursor.com>
There was a problem hiding this comment.
Stale comment
Deep code review
Task 7.6b: a JNI
SIGSEGV/SIGABRTfromcrashNativeis retained as an NDK crash bundle on the next launch, and ReportHandlerBridge completes that relaunch on the bounded path (by=recovery) rather than minting a JS handle.The new suite matches the surrounding e2e patterns (airplane +
clearAndroidBundlesincludingndk/, nonce-scopedstartRun, debug-only, handler installed beforelaunch()as a probe).crashNativeis a non-sync TurboModulevoidmethod, so the crashing marker is queued before the JNI signal;ReportHandlerBridge.dispatchtreats any non-BugseeReportHandlerThreadasby=recoveryand returns before a sink emit, which is exactly what case 3 pins. I did not run the device suite here (no handset / nonode_modulesin this environment). CI at review:androidand iOS unit/compat jobs green;lint, typecheck, unit, mutationandios e2ewere still pending.Findings
P2 Medium —
examples/bare/scenarios/native.ts:82(and the Task 7.6b plan rewrite)This PR changes 7.6b case 3 from “the report handler sees it” to “JS must not see it”, and the test actually requires
by=recovery, noBUGSEE_E2E native after type=crash, and noby=js. That is a user-visible contract: an Android app cannot annotate an NDK crash recovered at the nextlaunch()viaonAfterReportCreated.The published handler docs still describe the opposite of that NDK relaunch path:
BugseeReportHandler(packages/react-native/src/report/types.ts): “Register beforelaunch()to see reports the SDK recovers at launch” and “Android gives the live 25 s when the SDK was launched from JS”.docs/design/2026-09-15-sdk-design.md(recovery routing): a crash recovered at the next JS-initiated launch “does reach JS” (the 3.4d Java path).Impact: A developer following the JSDoc will register a handler to add labels/attachments on native crashes. Java recovery (3.4d) still hits JS on
BugseeReportHandlerThread; NDK recovery on this relaunch does not. The edits silently never land.Scenario: App calls
setReportHandler({ onAfterReportCreated })thenlaunch(), user hits a SIGSEGV in a native module, next process start recovers the bundle onbugsee-report-handler-bounded, bridgecompleteUnminted(..., "recovery"), handler never runs, report uploads without the labels.Fix: Keep the test as the pin of current wrapper+SDK behavior, but say so in the published contract: Android NDK relaunch recovery is the bounded path and never reaches JS; the live 25 s path is the Java/uncaught-exception relaunch (3.4d). Update
types.tsand the design-doc recovery paragraph in this PR (the plan was updated; those two were not). Also invert theinstallNativeHandlercomment — the handler is a negative probe, not a delivery path.No other P0–P3. I looked at early
force-stopduring dump,awaitBundles(1)racing a non-crash zip,Fatal signalwithout a pid, and MetrorealpathSync; none had a concrete false-pass or production-break scenario in this diff.
- Overall risk: Low
- Merge recommendation: Safe to merge as a device pin of current NDK recovery. Do not treat JS handler delivery for native crashes as done; update the public JSDoc (or file that as the immediate follow-up) so 3.4d and 7.6b are not documented as the same path.
- Most important to fix: The
BugseeReportHandler/ design-doc contract still claims JS-initiated launch recovery reaches JS; this PR now proves NDK relaunch does not.- Positives: Case 3 uses a registered handler as a negative probe (vacuous without it). Isolation matches 3.4d (
rm -rf files/bugsee_data, not justbundles/). SIGSEGV vs SIGABRT are separate runs with a clear between them. Metro realpath of thenode_modulestrees is a reasonable worktree fix and does not change a normal clone’s resolved layout.Sent by Cursor Automation: Bugsee code review
Co-authored-by: Cursor <cursoragent@cursor.com>
There was a problem hiding this comment.
Deep code review (re-review at 445b123)
Task 7.6b still pins the right thing: a JNI SIGSEGV / SIGABRT from crashNative is retained as an NDK crash bundle, and on the next JS launch() ReportHandlerBridge.dispatch takes the non-live thread short-circuit (completeUnminted(..., "recovery")) rather than minting a JS handle. Case 3 requires completed by=recovery phase=after, no BUGSEE_E2E native after type=crash, and no by=js. That is a real split from Task 3.4d (Java relaunch does reach JS on BugseeReportHandlerThread).
Prior finding
P2 fully addressed (PRRT_kwDOTk5MSM6nmrAg). 445b123 names the split in the published contract:
BugseeReportHandlerandsetReportHandler: Java uncaught-exception relaunch launched from JS still gets the live 25 s onBugseeReportHandlerThread; an NDK relaunch and early bounded recovery never reach JS.- The design-doc Android recovery paragraph now says the same, and keeps the 3.4d evidence (
deadline=25000,by=js) next to the 7.6b evidence (completed by=recovery phase=after, neverby=js). installNativeHandleris documented as a negative probe, not a delivery path.
This pass
I re-read the suite against ReportHandlerBridge, startRun / clearAndroidBundles (full files/bugsee_data, including ndk/), crashNative (JNI null store / abort(), -O0), and 3.4d's kill -11 case. Isolation, the observe handler-before-launch(), and waiting on by=recovery before awaitBundles all match the surrounding e2e pattern. Abort still goes through runKind, so it cannot silently take the live path either — the wait for by=recovery would time out.
No new P0–P3. I looked at a late Fatal signal without a pid, awaitBundles(1) racing a non-crash zip, Metro realpathSync of the node_modules trees, and a delayed by=js after the 2 s beat; none had a concrete false-pass or production-break scenario in this diff. I did not run the device suite here (no handset in this environment).
- Overall risk: Low
- Merge recommendation: Safe to merge.
- Most important to fix: Nothing remaining in this PR. Do not treat JS handler delivery for native crashes as done — that is now an explicit, documented non-goal of the NDK relaunch path.
- Positives: Case 3 uses a registered handler as a negative probe. SIGSEGV vs SIGABRT are separate runs with a clear between them. The public JSDoc now matches what the device test actually proves.
Sent by Cursor Automation: Bugsee code review


Summary
by=recovery, noBUGSEE_E2E native after type=crash, and noby=js.Test plan
526df46)AMRJCP4718402860, checkout526df46, Metro port 8091:native-crash.test.ts4/4Fatal signal 11, thenBugseeRN: report handler - completed by=recovery phase=after,exception_type: native, signal name SIGSEGV number 11,segv after markers: []Fatal signal 6, same recovery shape, signal name SIGABRT number 6, noby=jsMade with Cursor