digidem / digidem/comapeo-core-react-native
Sentry: propagate RN trace_id to FGS so cold-start spans land on one trace
Nobody has claimed this yet.
- Dominant language
- Kotlin
- Stars
- 1
- Forks
- 0
- Avg merge
- 8h 24m
- Merged PRs (30d)
- 9
Description
Context
Cold-start currently produces three separate Sentry traces:
- RN side:
App Starttransaction (app.start.cold/app.start.warm), trace owned by@sentry/react-native. - FGS side:
comapeo.boottransaction, trace owned by the FGS-processsentry-androidSDK (SentryFgsBridge). - Node side:
boot.loader-init,boot.import-index,boot.listen-control,boot.manager-init— inherit the FGS trace via--sentryTraceargv →Sentry.continueTrace.
Result: in Sentry's Performance view you can find RN's App Start or the comapeo.boot trace, but not the cold-start end-to-end on a single timeline. Comparing "what was holding up first paint — RN or Node?" requires manually correlating timestamps across two traces.
Proposal
Stamp the activity's current sentry-trace + baggage headers on the start-service intent. The FGS picks them up in onStartCommand, calls Sentry.continueTrace(...) before SentryFgsBridge.startBootTransaction, and comapeo.boot becomes a child transaction of RN's App Start on the same trace.
Node still inherits via --sentryTrace argv (no change there). All three layers land on a single trace.
Implementation sketch
-
ComapeoCoreReactActivityLifecycleListener.actionOnService— before thestartForegroundService/startServicecall, read the current scope's propagation context and stamp the headers:val scopes = io.sentry.Sentry.getCurrentScopes() val sentryTrace = scopes.propagationContext.toSentryTrace().value val baggage = scopes.propagationContext.toBaggage()?.toHeaderString(null) intent.putExtra(EXTRA_SENTRY_TRACE, sentryTrace) baggage?.let { intent.putExtra(EXTRA_SENTRY_BAGGAGE, it) } -
ComapeoCoreService.onStartCommand— read both headers from the intent and forward toNodeJSService:nodeJSService.parentSentryTrace = intent?.getStringExtra(EXTRA_SENTRY_TRACE) nodeJSService.parentSentryBaggage = intent?.getStringExtra(EXTRA_SENTRY_BAGGAGE) -
NodeJSService.start— callSentry.continueTraceimmediately beforestartBootTransactionso the boot transaction inherits trace_id + parent_span_id:val parentTrace = parentSentryTrace if (parentTrace != null) { io.sentry.Sentry.continueTrace(parentTrace, parentSentryBaggage?.let { listOf(it) }) } val tx = SentryFgsBridge.startBootTransaction(backdatedStart, bootKind) -
Constants for the intent extras in
Actions.ktor alongsideEXTRA_SERVICE_START_ELAPSED_MS. -
iOS is single-process so this is unnecessary there —
@sentry/react-native's scope is already shared withSentryNativeBridge's captures. No iOS change.
Considerations
- System-restart case. When Android restarts the FGS without an intent (already handled today —
boot.kind: system-restart), no trace headers arrive;comapeo.bootstarts a fresh trace. Same fallback asboot.fgs-launchbeing skipped. Already covered by existing tag. - Cold start before RN init. If the FGS launches before RN has called
initSentry(),Sentry.getCurrentScopes()returns a no-op hub and headers will be empty/null. The intent stamping should null-guard so we don't write garbage headers; the FGS-side null-check then skipscontinueTrace. Falls through to current behaviour (own trace). - Sampling. RN's App Start transaction is force-sampled (sampled by AppStart integration). If RN inherits a non-sampled trace... actually irrelevant — RN is the root.
- Trace continuity past
App Start. RN's App Start transaction finishes within a couple of seconds. After it ends, the propagation context still holds the trace_id for subsequent activity. FGS-sidecomapeo.bootopens during this window so it inherits the right trace. Verified: even after App Start finishes, the propagation_context.trace_id persists on the scope.
Acceptance criteria
- On Android cold start, the trace_id of
App Start(RN-side) equals the trace_id ofcomapeo.boot(FGS-side) equals the trace_id ofboot.import-index(Node-side). - Searching Sentry by either side's trace_id surfaces all three transactions in the Performance > Trace view.
Related
- PR #63 — Phase 3 / 10 (this is the follow-up after offline transport + nanos fixes landed)
docs/sentry-integration-plan.md§7.4 — native instrumentation taxonomy. Update §7.4.2 to note cross-process trace propagation when this lands.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with ComapeoCoreReactActivityLifecycleListener.actionOnService, ComapeoCoreService.onStartCommand, and NodeJSService.start, then inspect Actions.kt and the existing SentryFgsBridge boot flow. Verify the Android cold-start path and existing system-restart handling before making the change. Done means App Start, comapeo.boot, and boot.import-index share one trace_id in Sentry, with the documented §7.4.2 update.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- android, kotlin
- Domain
- mobile, observability
- Issue type
- Feature
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 68/100