Skip to content

fix(tracing): Recover app start data when first navigation transaction is discarded#5833

Merged
antonis merged 5 commits intomainfrom
antonis/app-start-nav
Mar 19, 2026
Merged

fix(tracing): Recover app start data when first navigation transaction is discarded#5833
antonis merged 5 commits intomainfrom
antonis/app-start-nav

Conversation

@antonis
Copy link
Contributor

@antonis antonis commented Mar 18, 2026

📢 Type of change

  • Bugfix

📜 Description

When the first navigation idle transaction is discarded at spanEnd (e.g., by ignoreEmptyRouteChangeTransactions because the navigation container was registered after the idle transaction finished), firstStartedActiveRootSpanId remained permanently locked to the dead span. Since no transaction event was ever created for it, processEvent never ran, and all subsequent transactions failed the span ID check — silently losing app start data for the entire session.

Changes

  1. Lazy sampled check at next spanStart — Store a reference to the locked root span. When the next root span starts and firstStartedActiveRootSpanId is already set, check if the locked span was unsampled (e.g., dropped by ignoreEmptyRouteChangeTransactions or ignoreEmptyBackNavigation). If so, reset the lock and allow the new span to carry app start data. This check happens at spanStart rather than spanEnd to avoid a listener ordering issue — the discard listeners that set _sampled = false run after the app start listener in registration order, so checking at spanEnd would always see the span as still sampled.

  2. Early appStartEndData timestamp — Set the end timestamp in _captureAppStart before awaiting fetchNativeFrames, closing a secondary timing race where processEvent could run before the timestamp was available. Added _updateAppStartEndFrames helper to update frames after the async fetch without triggering the overwrite warning.

  3. Permanent failure handling in attachAppStartToTransactionEvent — All failure paths after the span ID check now set appStartDataFlushed = true to prevent wasteful retries, since these conditions (native returns null, has_fetched, missing timestamps, negative/excessive duration) won't change within the same app start. Added debug log to the previously silent appStartDataFlushed early return.

💡 Motivation and Context

Fixes #5831

User logs from the support ticket confirmed the exact root cause. The sequence:

  1. First navigation idle span "Route Change" starts → firstStartedActiveRootSpanId locked
  2. registerNavigationContainer() is called ~7 seconds later (after the idle transaction finished)
  3. ignoreEmptyRouteChangeTransactions discards the empty transaction (_sampled = false at spanEnd)
  4. No transaction event created → processEvent never runs
  5. firstStartedActiveRootSpanId permanently locked to dead span → all subsequent transactions fail span ID check

The "intermittent" nature is because the timing of registerNavigationContainer varies between launches — sometimes it's fast enough (app start works), sometimes it's late (app start lost).

💚 How did you test it?

  • Added test "Attaches app start to next transaction when first root span was dropped" — reproduces the exact reported scenario (first span locked, unsampled after ending, second span detects this at spanStart and succeeds)
  • Added test "Sets appStartDataFlushed when native returns null to prevent wasteful retries" — verifies fetchNativeAppStart is only called once
  • Added test "Sets appStartDataFlushed when has_fetched is true to prevent wasteful retries"
  • Added test "Sets appStartDataFlushed when app start end timestamp is before app start timestamp"
  • All 50 existing + new tests pass
  • Full suite: 1128 tests pass, build, lint, and circular dependency checks pass

📝 Checklist

  • I added tests to verify changes
  • No new PII added or SDK only sends newly added PII if sendDefaultPII is enabled
  • I updated the docs if needed.
  • I updated the wizard if needed.
  • All tests passing
  • No breaking changes

🔮 Next steps

…n is discarded

When the first navigation idle transaction is discarded at spanEnd
(e.g., by ignoreEmptyRouteChangeTransactions because the navigation
container was registered too late), firstStartedActiveRootSpanId
remained permanently locked to the dead span. Since no transaction
event was ever created for it, processEvent never ran, and all
subsequent transactions failed the span ID check — silently losing
app start data for the entire session.

Add a spanEnd listener that resets the lock when the first root span
is unsampled at end time. Also set appStartEndData timestamp before
awaiting fetchNativeFrames to close a secondary timing race, and
improve failure recovery in attachAppStartToTransactionEvent.

Fixes #5831

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@github-actions
Copy link
Contributor

github-actions bot commented Mar 18, 2026

Semver Impact of This PR

None (no version bump detected)

📋 Changelog Preview

This is how your changes will appear in the changelog.
Entries from this PR are highlighted with a left border (blockquote style).


  • fix(tracing): Recover app start data when first navigation transaction is discarded by antonis in #5833
  • chore(deps): update Android SDK to v8.36.0 by github-actions in #5812
  • Add expoUpdatesListenerIntegration that records breadcrumbs for Expo Updates lifecycle events by alwx in #5795
  • chore(deps): update Sentry Android Gradle Plugin to v6.2.0 by github-actions in #5836
  • fix(ci): Update Appium version to fix Sauce Labs metrics tests by antonis in #5835
  • chore(deps): update JavaScript SDK to v10.44.0 by github-actions in #5832
  • fix(tracing): Fix native frames measurements dropped for idle transactions by antonis in #5813
  • feat(core): Support SENTRY_ENVIRONMENT in bare React Native builds by antonis in #5823
  • chore(deps): bump tar to ^7.5.11 by antonis in #5824
  • chore(deps): bump actions/create-github-app-token from 2.2.1 to 3.0.0 by dependabot in #5822
  • chore(deps): bump dorny/paths-filter from 3.0.2 to 4.0.1 by dependabot in #5820
  • chore(deps): bump reactivecircus/android-emulator-runner from 2.35.0 to 2.37.0 by dependabot in #5818
  • chore(deps): bump getsentry/craft/.github/workflows/changelog-preview.yml from 2.23.2 to 2.24.1 by dependabot in #5821
  • chore(deps): bump getsentry/craft from 2.23.2 to 2.24.1 by dependabot in #5819
  • chore(deps): bump undici from 6.23.0 to 6.24.1 by dependabot in #5817
  • chore(deps): bump flatted from 3.3.1 to 3.4.1 by dependabot in #5816
  • Ref: remove yarn from stub update by lucas-zimerman in #5811
  • Ref(CI): Unify stub update with android update by lucas-zimerman in #5807

🤖 This preview updates automatically when you update the PR.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Unsubscribe spanEnd listener after it fires to prevent listener leak
- Add _updateAppStartEndFrames helper instead of direct module-level mutation
- Make all processEvent failure paths set appStartDataFlushed = true
  (these conditions won't change within the same app start, retrying is wasteful)
- Add comment about _sampled internal coupling in test
- Fix unnecessary type assertions (lint)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ead of spanEnd

The spanEnd listener approach had a listener ordering bug: the app start
listener registered at spanStart time runs BEFORE the navigation discard
listeners (ignoreEmptyRouteChangeTransactions, ignoreEmptyBackNavigation)
that set _sampled = false. So our check always saw the span as sampled.

Instead, store a reference to the locked root span and check its sampled
status lazily at the next spanStart. By then, the old span has fully
completed and _sampled has been set to false by the discard listeners.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@antonis
Copy link
Contributor Author

antonis commented Mar 18, 2026

@cursor review

@antonis
Copy link
Contributor Author

antonis commented Mar 18, 2026

@sentry review

Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

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

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

@antonis antonis marked this pull request as ready for review March 18, 2026 11:40
@lucas-zimerman
Copy link
Collaborator

Unrelated to the PR:
It has the potential of fixing the issue, but I think the implemented logic for app start is kinda flaky. It would be nice if the app start data was independent and didn't depend on a child span.

The question lies on the following topics:

  • Should we send app start data if sample rate is 0 or a lower value.
  • Should it be always be sampled, ignoring what was set on the sample rate?

Copy link
Collaborator

@lucas-zimerman lucas-zimerman left a comment

Choose a reason for hiding this comment

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

The fix looks good!

@antonis antonis added the ready-to-merge Triggers the full CI test suite label Mar 18, 2026
@github-actions
Copy link
Contributor

Android (legacy) Performance metrics 🚀

  Plain With Sentry Diff
Startup time 480.59 ms 524.72 ms 44.14 ms
Size 43.75 MiB 48.07 MiB 4.32 MiB

Baseline results on branch: main

Startup times

Revision Plain With Sentry Diff
4a17c8f+dirty 406.62 ms 400.58 ms -6.04 ms
df1f7df+dirty 442.64 ms 427.16 ms -15.48 ms
a483f9f+dirty 396.82 ms 453.28 ms 56.46 ms
60cd796+dirty 445.84 ms 492.45 ms 46.61 ms
5c16cdc+dirty 423.48 ms 452.35 ms 28.88 ms
80e4616+dirty 411.58 ms 462.12 ms 50.54 ms
55b77fc+dirty 411.87 ms 417.16 ms 5.29 ms
bca62c0+dirty 414.36 ms 451.06 ms 36.70 ms
0b64753+dirty 448.67 ms 474.61 ms 25.94 ms
4e6d7d7+dirty 480.73 ms 515.73 ms 35.00 ms

App size

Revision Plain With Sentry Diff
4a17c8f+dirty 43.75 MiB 47.99 MiB 4.24 MiB
df1f7df+dirty 43.75 MiB 48.08 MiB 4.33 MiB
a483f9f+dirty 43.75 MiB 48.41 MiB 4.66 MiB
60cd796+dirty 43.75 MiB 48.07 MiB 4.32 MiB
5c16cdc+dirty 17.75 MiB 19.68 MiB 1.94 MiB
80e4616+dirty 43.75 MiB 48.55 MiB 4.80 MiB
55b77fc+dirty 43.75 MiB 47.99 MiB 4.24 MiB
bca62c0+dirty 43.75 MiB 48.41 MiB 4.66 MiB
0b64753+dirty 17.75 MiB 19.70 MiB 1.95 MiB
4e6d7d7+dirty 43.75 MiB 48.40 MiB 4.64 MiB

@github-actions
Copy link
Contributor

iOS (legacy) Performance metrics 🚀

  Plain With Sentry Diff
Startup time 1210.74 ms 1214.27 ms 3.52 ms
Size 3.38 MiB 4.72 MiB 1.34 MiB

Baseline results on branch: main

Startup times

Revision Plain With Sentry Diff
ea3e26e+dirty 1229.13 ms 1228.46 ms -0.67 ms
80e4616+dirty 1221.32 ms 1225.64 ms 4.32 ms
818a608+dirty 1205.76 ms 1208.00 ms 2.24 ms
77061ed+dirty 1233.16 ms 1234.88 ms 1.71 ms
bef3709+dirty 1222.07 ms 1220.24 ms -1.83 ms
a206511+dirty 1185.00 ms 1186.35 ms 1.35 ms
74979ac+dirty 1210.49 ms 1213.31 ms 2.82 ms
a2bb688+dirty 1223.53 ms 1232.90 ms 9.37 ms
8a868fe+dirty 1221.50 ms 1230.78 ms 9.28 ms
d590428+dirty 1211.77 ms 1220.51 ms 8.75 ms

App size

Revision Plain With Sentry Diff
ea3e26e+dirty 3.41 MiB 4.58 MiB 1.17 MiB
80e4616+dirty 3.38 MiB 4.60 MiB 1.22 MiB
818a608+dirty 2.63 MiB 3.91 MiB 1.28 MiB
77061ed+dirty 2.63 MiB 3.98 MiB 1.34 MiB
bef3709+dirty 3.38 MiB 4.78 MiB 1.40 MiB
a206511+dirty 3.41 MiB 4.67 MiB 1.25 MiB
74979ac+dirty 3.38 MiB 4.60 MiB 1.22 MiB
a2bb688+dirty 2.63 MiB 3.99 MiB 1.36 MiB
8a868fe+dirty 3.38 MiB 4.60 MiB 1.22 MiB
d590428+dirty 3.38 MiB 4.78 MiB 1.39 MiB

@github-actions
Copy link
Contributor

Android (new) Performance metrics 🚀

  Plain With Sentry Diff
Startup time 389.79 ms 416.14 ms 26.36 ms
Size 43.94 MiB 48.93 MiB 4.99 MiB

Baseline results on branch: main

Startup times

Revision Plain With Sentry Diff
70250df+dirty 418.08 ms 480.84 ms 62.76 ms
8d89cc9+dirty 357.69 ms 415.79 ms 58.10 ms
1853710+dirty 360.67 ms 396.28 ms 35.61 ms
55b77fc+dirty 410.46 ms 414.11 ms 3.65 ms
69602ce+dirty 375.37 ms 405.28 ms 29.91 ms
c1573b3+dirty 355.65 ms 448.82 ms 93.17 ms
90afdd3+dirty 367.79 ms 404.84 ms 37.05 ms
955f2eb+dirty 388.13 ms 433.56 ms 45.44 ms
80e4616+dirty 427.31 ms 461.15 ms 33.84 ms
276d348+dirty 356.30 ms 405.27 ms 48.97 ms

App size

Revision Plain With Sentry Diff
70250df+dirty 43.94 MiB 48.91 MiB 4.97 MiB
8d89cc9+dirty 7.15 MiB 8.41 MiB 1.26 MiB
1853710+dirty 7.15 MiB 8.41 MiB 1.26 MiB
55b77fc+dirty 43.94 MiB 48.82 MiB 4.88 MiB
69602ce+dirty 7.15 MiB 8.41 MiB 1.26 MiB
c1573b3+dirty 7.15 MiB 8.42 MiB 1.27 MiB
90afdd3+dirty 7.15 MiB 8.43 MiB 1.28 MiB
955f2eb+dirty 7.15 MiB 8.42 MiB 1.27 MiB
80e4616+dirty 43.94 MiB 49.38 MiB 5.44 MiB
276d348+dirty 7.15 MiB 8.42 MiB 1.26 MiB

@github-actions
Copy link
Contributor

iOS (new) Performance metrics 🚀

  Plain With Sentry Diff
Startup time 1212.57 ms 1212.57 ms -0.00 ms
Size 3.38 MiB 4.72 MiB 1.34 MiB

Baseline results on branch: main

Startup times

Revision Plain With Sentry Diff
ea3e26e+dirty 1216.61 ms 1214.15 ms -2.47 ms
80e4616+dirty 1206.90 ms 1205.94 ms -0.96 ms
818a608+dirty 1218.84 ms 1223.18 ms 4.34 ms
77061ed+dirty 1210.77 ms 1218.45 ms 7.68 ms
bef3709+dirty 1217.79 ms 1225.33 ms 7.54 ms
a206511+dirty 1225.02 ms 1223.74 ms -1.28 ms
74979ac+dirty 1212.33 ms 1212.54 ms 0.21 ms
a2bb688+dirty 1244.82 ms 1238.60 ms -6.22 ms
8a868fe+dirty 1206.85 ms 1215.04 ms 8.19 ms
d590428+dirty 1221.23 ms 1225.27 ms 4.03 ms

App size

Revision Plain With Sentry Diff
ea3e26e+dirty 3.41 MiB 4.58 MiB 1.17 MiB
80e4616+dirty 3.38 MiB 4.60 MiB 1.22 MiB
818a608+dirty 3.19 MiB 4.48 MiB 1.29 MiB
77061ed+dirty 3.19 MiB 4.54 MiB 1.36 MiB
bef3709+dirty 3.38 MiB 4.78 MiB 1.40 MiB
a206511+dirty 3.41 MiB 4.67 MiB 1.25 MiB
74979ac+dirty 3.38 MiB 4.60 MiB 1.22 MiB
a2bb688+dirty 3.19 MiB 4.56 MiB 1.37 MiB
8a868fe+dirty 3.38 MiB 4.60 MiB 1.22 MiB
d590428+dirty 3.38 MiB 4.78 MiB 1.39 MiB

@antonis antonis merged commit cfd2d4f into main Mar 19, 2026
76 of 79 checks passed
@antonis antonis deleted the antonis/app-start-nav branch March 19, 2026 07:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ready-to-merge Triggers the full CI test suite

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Intermittent Cold/Warm App Start Traces Missing in React Native SDK v7.12.1

2 participants