Skip to content

test(e2e): recover an Android native crash without a JS handler - #15

Merged
krassx merged 2 commits into
mainfrom
feat/phase-7-native-crash
Sep 30, 2026
Merged

krassx merged 2 commits into
mainfrom
feat/phase-7-native-crash

Conversation

@krassx

@krassx krassx commented Sep 30, 2026

Copy link
Copy Markdown
Contributor

Summary

  • An Android native crash (SIGSEGV and SIGABRT) is recovered by the NDK report handler on the bounded path, and the relaunch does not emit a JS handler line.
  • Case 3 requires by=recovery, no BUGSEE_E2E native after type=crash, and no by=js.

Test plan

  • Review: spec met, 0 critical / 0 important / 0 minor (526df46)
  • WOD_LX1 AMRJCP4718402860, checkout 526df46, Metro port 8091: native-crash.test.ts 4/4
  • SIGSEGV: Fatal signal 11, then BugseeRN: report handler - completed by=recovery phase=after, exception_type: native, signal name SIGSEGV number 11, segv after markers: []
  • SIGABRT: Fatal signal 6, same recovery shape, signal name SIGABRT number 6, no by=js

Made with Cursor

Co-authored-by: Cursor <cursoragent@cursor.com>

@cursor cursor Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale comment

Deep code review

Task 7.6b: a JNI SIGSEGV / SIGABRT from crashNative is 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 + clearAndroidBundles including ndk/, nonce-scoped startRun, debug-only, handler installed before launch() as a probe). crashNative is a non-sync TurboModule void method, so the crashing marker is queued before the JNI signal; ReportHandlerBridge.dispatch treats any non-BugseeReportHandlerThread as by=recovery and returns before a sink emit, which is exactly what case 3 pins. I did not run the device suite here (no handset / no node_modules in this environment). CI at review: android and iOS unit/compat jobs green; lint, typecheck, unit, mutation and ios e2e were 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, no BUGSEE_E2E native after type=crash, and no by=js. That is a user-visible contract: an Android app cannot annotate an NDK crash recovered at the next launch() via onAfterReportCreated.

The published handler docs still describe the opposite of that NDK relaunch path:

  • BugseeReportHandler (packages/react-native/src/report/types.ts): “Register before launch() 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 }) then launch(), user hits a SIGSEGV in a native module, next process start recovers the bundle on bugsee-report-handler-bounded, bridge completeUnminted(..., "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.ts and the design-doc recovery paragraph in this PR (the plan was updated; those two were not). Also invert the installNativeHandler comment — the handler is a negative probe, not a delivery path.

No other P0–P3. I looked at early force-stop during dump, awaitBundles(1) racing a non-crash zip, Fatal signal without a pid, and Metro realpathSync; none had a concrete false-pass or production-break scenario in this diff.


  1. Overall risk: Low
  2. 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.
  3. 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.
  4. Positives: Case 3 uses a registered handler as a negative probe (vacuous without it). Isolation matches 3.4d (rm -rf files/bugsee_data, not just bundles/). SIGSEGV vs SIGABRT are separate runs with a clear between them. Metro realpath of the node_modules trees is a reasonable worktree fix and does not change a normal clone’s resolved layout.
Open in Web View Automation 

Sent by Cursor Automation: Bugsee code review

Comment thread examples/bare/scenarios/native.ts
Co-authored-by: Cursor <cursoragent@cursor.com>

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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:

  • BugseeReportHandler and setReportHandler: Java uncaught-exception relaunch launched from JS still gets the live 25 s on BugseeReportHandlerThread; 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, never by=js).
  • installNativeHandler is 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).


  1. Overall risk: Low
  2. Merge recommendation: Safe to merge.
  3. 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.
  4. 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.
Open in Web View Automation 

Sent by Cursor Automation: Bugsee code review

@krassx
krassx merged commit c11119d into main Sep 30, 2026
16 checks passed
@krassx
krassx deleted the feat/phase-7-native-crash branch September 30, 2026 16:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant