You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Rewrite RN's app start end customization onto a span-returning API consistent with Flutter and Cocoa. Today appLoaded() only moves the end timestamp and returns nothing, so users can't break down extended launch work.
// Simple: extend the app start window, then finishSentry.extendAppStart();awaitinitializeRemoteConfig();Sentry.finishAppStart();
// With child spans: instrument the extended regionSentry.extendAppStart();constspan=Sentry.getExtendedAppStartSpan();constconfigSpan=span.startChild({op: 'app.init',description: 'fetch remote config'});awaitloadRemoteConfig();configSpan.finish();Sentry.finishAppStart();
getExtendedAppStartSpan(): Span — returns the span for attaching children; a no-op span when there's no active extension.
finishAppStart() ≡ .finish() on the returned span.
Lifecycle
extendAppStart: creates the span (start = call time). No-ops if app start already finished, if none is in progress, or on repeat calls (first wins).
getExtendedAppStartSpan: returns the extended span, else a no-op span.
finishAppStart: no-ops if not extended or called twice. Doesn't finalize directly — the parent (auto-generated, waitForChildren) finalizes when its last child finishes and trims its end to that child. Open children of the extended span are finished cancelled at the finish-call time.
Duration & measurement
Parent duration = end of the last child to finish (trim-to-last-child); the extended span is just one participant.
App start measurement = that final duration (process start → last child), set at finalization. The normal completion-time measurement must be suppressed in extended mode so the early (non-extended) value doesn't win.
Timeout
Parent's auto-finish deadline is 30s. If finishAppStart() is never called, the transaction auto-finishes on the deadline (unfinished children → deadlineExceeded, snapped to transaction end), is still captured, but the app.vitals.start measurement is suppressed — never emit a ~30s app start. Same for standalone and ui.load.
Standalone & non-standalone
Both supported, same mechanism: standalone uses the app-start tracer, non-standalone the ui.load transaction (also waitForChildren + deadline). In non-standalone the duration is bounded by anything keeping ui.load open (TTID, other children), so it can include spans unrelated to the extension.
Metrics hygiene (decide before the app.vitals.start migration)
Extended app starts skew the duration distribution. Tag them (an app.vitals.start.reason value or an extended: true flag) so they're separable from the p75/p90 baseline — land before the migration or it's a backfill.
RN specifics
Replace appLoaded() directly (experimental — no deprecation cycle) and fold in the already-deprecated captureAppStart(). Document the migration to extendAppStart / children / finishAppStart.
RN's waitForChildren equivalent is the cancel-deferred path: auto-capture schedules captureStandaloneAppStart() via setTimeout(…, 0); extendAppStart() must cancel that deferred send and keep the transaction open until finishAppStart(), producing the same trim-to-last-child end. Reuse the cached-response path that bypasses the native has_fetched: true guard.
Rewrite RN's app start end customization onto a span-returning API consistent with Flutter and Cocoa. Today
appLoaded()only moves the end timestamp and returns nothing, so users can't break down extended launch work.Reference: getsentry/sentry-cocoa#6886 (Done,
SentryExtendedAppLaunchManager). Siblings: getsentry/sentry-dart#3767, getsentry/sentry-java#5553 — shared contract, RN-specific sections below.API
extendAppStart(): void— creates the extended app start span (opapp.start.extended_app_start, description"Extended App Start").finishAppStart(): void— finishes it; triggers finalization.getExtendedAppStartSpan(): Span— returns the span for attaching children; a no-op span when there's no active extension.Lifecycle
extendAppStart: creates the span (start = call time). No-ops if app start already finished, if none is in progress, or on repeat calls (first wins).getExtendedAppStartSpan: returns the extended span, else a no-op span.finishAppStart: no-ops if not extended or called twice. Doesn't finalize directly — the parent (auto-generated,waitForChildren) finalizes when its last child finishes and trims its end to that child. Open children of the extended span are finishedcancelledat the finish-call time.Duration & measurement
Timeout
finishAppStart()is never called, the transaction auto-finishes on the deadline (unfinished children →deadlineExceeded, snapped to transaction end), is still captured, but theapp.vitals.startmeasurement is suppressed — never emit a ~30s app start. Same for standalone andui.load.Standalone & non-standalone
ui.loadtransaction (alsowaitForChildren+ deadline). In non-standalone the duration is bounded by anything keepingui.loadopen (TTID, other children), so it can include spans unrelated to the extension.Metrics hygiene (decide before the
app.vitals.startmigration)app.vitals.start.reasonvalue or anextended: trueflag) so they're separable from the p75/p90 baseline — land before the migration or it's a backfill.RN specifics
appLoaded()directly (experimental — no deprecation cycle) and fold in the already-deprecatedcaptureAppStart(). Document the migration toextendAppStart/ children /finishAppStart.waitForChildrenequivalent is the cancel-deferred path: auto-capture schedulescaptureStandaloneAppStart()viasetTimeout(…, 0);extendAppStart()must cancel that deferred send and keep the transaction open untilfinishAppStart(), producing the same trim-to-last-child end. Reuse the cached-response path that bypasses the nativehas_fetched: trueguard.app.start.cold/app.start.warm→ allow nesting; or make it be controlled somehow #5936 (child nesting underapp.start.cold/warm; currently Blocked).Acceptance criteria
extendAppStart()creates the span before finish; no-ops when too late, not started, or called repeatedly.getExtendedAppStartSpan()returns the span, else a no-op span (including after finish).Extended App Start.finishAppStart()no-ops if not extended or called twice; finalizes via the deferred-cancel path with transaction end = last child.cancelled; on deadline →deadlineExceeded, snapped to end.app.vitals.startsuppressed.appLoaded()replaced,captureAppStart()folded in, migration documented.