Addresses the high- and medium-priority issues from the
review subagent's pass over Phase 1+2a+2b:
Boot transaction lifecycle (was: review #1, #15)
- applyAndEmit now closes bootTx and drains in-flight phase
spans on STOPPING / STOPPED transitions too — not just
STARTED / ERROR. stop()-from-STARTING transitions to
STOPPING (rule 3 of deriveLifecycleState) and bypassed
both terminals; destroy() forcing STOPPED-via-stopRequested
did the same. Status mapped: STARTED→ok, ERROR→
internal_error, STOPPING/STOPPED→cancelled.
- startBootTransaction now passes a TransactionContext
carrying TracesSamplingDecision(true, 1.0). The previous
TransactionOptions-only setup didn't actually force
sampling, so with the SDK default tracesSampleRate=0.0 the
boot transaction was dropped before reaching the wire.
SentryConfig misconfig handling (was: review #3)
- Both Kotlin and Swift readers used to crash on (DSN-set,
environment-missing) — meant to be "fail loud" but a stale
prebuild from before the validation was added would crash
every cold start with no recovery. Now log loud (System.err
on Android since android.util.Log isn't mocked on JVM
tests; NSLog on iOS) and return null (Sentry off). Updated
test renamed to assert "returns null, doesn't throw".
Span op/description ordering (was: review #19)
- transaction.startChild(op, description) — op is the indexed
dashboard column. Was passing ("boot", "boot.<phase>"),
swapped to ("boot.<phase>", human-readable description) so
the dashboard groups by the phase taxonomy that matches
the bench backend's boot-spans.js helper.
Plugin idempotency (was: review #7)
- Previously, dropping `props.sentry` from the plugin
registration left stale meta-data / plist entries from a
previous prebuild (with `expo prebuild --no-clean`). Plugin
now passes through a no-Sentry cleanup mod that strips
every key it owns; consumer-owned keys (e.g. io.sentry.* set
by @sentry/react-native's plugin) are untouched.
messageerror payload truncation (was: review #8)
- src/sentry.ts now truncates the wrapped error message to
256 chars before forwarding to captureException. The
control-frame parser surfaces offending input verbatim,
which can include arbitrary bytes from a corrupted frame —
truncating keeps Sentry events small and readable.
IPC + SEND_ERROR_NATIVE breadcrumbs/events (was: review #9, #10)
- NodeJSIPC's onConnectionStateChange callback wired in the
FGS-side controlIpc construction; emits comapeo.ipc
breadcrumbs at info (warning on Error). Per §7.4.5.
- SEND_ERROR_NATIVE_TIMEOUT_MS firing now captures a
level=warning event with timeout:errorNativeForward tag.
Per §7.4.4.
Logging swallowed surprises (was: review low-priority)
- SentryFgsBridge's empty `catch (t: Throwable) {}` blocks
now Log.w so debug builds notice swallowed bridge / SDK
bugs.
Post-init bridge tests (was: review #6)
- New SentryFgsBridgeImplTest spins up a real Sentry hub via
the cross-platform Sentry.init(SentryOptions) path with an
in-memory ITransport. Covers: addBreadcrumb (no envelope on
its own), captureException + captureMessage (envelope
enqueued), startBootTransaction with global
tracesSampleRate=0.0 (must still reach transport thanks
to the TracesSamplingDecision override — regression test
for the §15 bug above), boot span lifecycle, finishSpan
with cancelled status, unknown level fallback to INFO.
All Sentry-related tests pass: 25 cases across
SentryConfigTest (8), SentryFgsBridgeTest (10),
SentryFgsBridgeImplTest (7).
Verified locally:
- npm run lint clean
- npx tsc --noEmit clean
- ./gradlew :comapeo-core-react-native:testDebugUnitTest
passes
- ./gradlew :comapeo-core-react-native:compileDebugKotlin
succeeds with sentry-android on the compile classpath
https://claude.ai/code/session_01PKvomnFyTYzSo5xn4k69DH