fix(tracing): Skip duplicate navigation span from withAnchor POP_TO#6439
fix(tracing): Skip duplicate navigation span from withAnchor POP_TO#6439antonis wants to merge 7 commits into
Conversation
…6434) Expo Router's `withAnchor` navigations can emit a second `POP_TO` dispatch to the destination route purely to stamp `initial: false` onto it. Since `startIdleNavigationSpan` runs on every `__unsafe_action__` dispatch and `POP_TO` was not filtered, this bookkeeping dispatch started a second idle navigation span that lingered until timeout and was sent as a spurious duplicate transaction (sometimes capturing in-flight HTTP/TTID children). Skip starting a navigation span for a `POP_TO` whose target is the route we are already on. The current route name is read from React Navigation's own `getCurrentRoute()` (raw name, same namespace as the action payload) rather than `latestRoute`, whose name may be rewritten by a route override provider such as Expo Router. Genuine `popTo`/`dismissTo` navigations to a different route are unaffected. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
864ccd2 to
2dd9cc9
Compare
Semver Impact of This PR⚪ None (no version bump detected) 📋 Changelog PreviewThis is how your changes will appear in the changelog.
🤖 This preview updates automatically when you update the PR. |
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit 2dd9cc9. Configure here.
📲 Install BuildsAndroid
|
iOS (legacy) Performance metrics 🚀
|
| Revision | Plain | With Sentry | Diff |
|---|---|---|---|
| 21a1e70+dirty | 3834.15 ms | 1218.43 ms | -2615.73 ms |
| 882f8ae+dirty | 3840.30 ms | 1224.41 ms | -2615.88 ms |
| 15d4514+dirty | 3840.17 ms | 1225.79 ms | -2614.38 ms |
| 0b5a379+dirty | 3828.91 ms | 1214.12 ms | -2614.79 ms |
| f3215d3+dirty | 3842.73 ms | 1219.33 ms | -2623.40 ms |
| ab203f9+dirty | 3848.07 ms | 1219.71 ms | -2628.35 ms |
| 6177334+dirty | 3834.85 ms | 1217.58 ms | -2617.28 ms |
| 5257d80+dirty | 3854.39 ms | 1234.28 ms | -2620.11 ms |
| 9474ead+dirty | 3864.29 ms | 1223.55 ms | -2640.74 ms |
| 1122a96+dirty | 3823.10 ms | 1218.64 ms | -2604.46 ms |
App size
| Revision | Plain | With Sentry | Diff |
|---|---|---|---|
| 21a1e70+dirty | 4.98 MiB | 6.46 MiB | 1.49 MiB |
| 882f8ae+dirty | 5.15 MiB | 6.70 MiB | 1.54 MiB |
| 15d4514+dirty | 5.15 MiB | 6.70 MiB | 1.55 MiB |
| 0b5a379+dirty | 5.15 MiB | 6.70 MiB | 1.54 MiB |
| f3215d3+dirty | 5.15 MiB | 6.67 MiB | 1.52 MiB |
| ab203f9+dirty | 4.98 MiB | 6.51 MiB | 1.53 MiB |
| 6177334+dirty | 5.15 MiB | 6.68 MiB | 1.53 MiB |
| 5257d80+dirty | 5.15 MiB | 6.69 MiB | 1.54 MiB |
| 9474ead+dirty | 5.15 MiB | 6.71 MiB | 1.55 MiB |
| 1122a96+dirty | 5.15 MiB | 6.68 MiB | 1.53 MiB |
Android (legacy) Performance metrics 🚀
|
| Revision | Plain | With Sentry | Diff |
|---|---|---|---|
| a5d243c+dirty | 424.52 ms | 485.18 ms | 60.66 ms |
| e5bb5f6+dirty | 430.09 ms | 459.96 ms | 29.87 ms |
| 7ac3378+dirty | 404.78 ms | 439.84 ms | 35.06 ms |
| 5789645+dirty | 426.82 ms | 495.42 ms | 68.60 ms |
| 853723c+dirty | 405.54 ms | 440.08 ms | 34.54 ms |
| 61cc206+dirty | 522.33 ms | 553.98 ms | 31.65 ms |
| 44c8b3f+dirty | 414.20 ms | 457.28 ms | 43.08 ms |
| 68ae91b+dirty | 416.44 ms | 477.56 ms | 61.12 ms |
| 7a89652+dirty | 537.76 ms | 567.84 ms | 30.08 ms |
| b0d3373+dirty | 557.66 ms | 579.42 ms | 21.76 ms |
App size
| Revision | Plain | With Sentry | Diff |
|---|---|---|---|
| a5d243c+dirty | 48.30 MiB | 53.54 MiB | 5.23 MiB |
| e5bb5f6+dirty | 49.74 MiB | 55.09 MiB | 5.34 MiB |
| 7ac3378+dirty | 43.75 MiB | 48.13 MiB | 4.37 MiB |
| 5789645+dirty | 49.74 MiB | 54.85 MiB | 5.11 MiB |
| 853723c+dirty | 48.30 MiB | 53.58 MiB | 5.28 MiB |
| 61cc206+dirty | 49.74 MiB | 55.36 MiB | 5.62 MiB |
| 44c8b3f+dirty | 48.30 MiB | 53.46 MiB | 5.15 MiB |
| 68ae91b+dirty | 49.74 MiB | 54.79 MiB | 5.05 MiB |
| 7a89652+dirty | 48.30 MiB | 53.60 MiB | 5.30 MiB |
| b0d3373+dirty | 48.30 MiB | 53.58 MiB | 5.28 MiB |
Android (new) Performance metrics 🚀
|
| Revision | Plain | With Sentry | Diff |
|---|---|---|---|
| 1e5d96d+dirty | 423.33 ms | 482.46 ms | 59.13 ms |
| a5d243c+dirty | 499.20 ms | 525.62 ms | 26.42 ms |
| ecf47a2+dirty | 457.21 ms | 498.10 ms | 40.89 ms |
| a0d8cf8+dirty | 533.71 ms | 564.25 ms | 30.54 ms |
| f170ec3+dirty | 505.96 ms | 551.88 ms | 45.92 ms |
| 9474ead+dirty | 432.18 ms | 481.92 ms | 49.73 ms |
| c151573+dirty | 485.39 ms | 495.18 ms | 9.79 ms |
| 7436d0f+dirty | 429.58 ms | 452.52 ms | 22.94 ms |
| 4e0b819+dirty | 599.31 ms | 654.31 ms | 55.00 ms |
| 5a23c47+dirty | 406.83 ms | 451.47 ms | 44.64 ms |
App size
| Revision | Plain | With Sentry | Diff |
|---|---|---|---|
| 1e5d96d+dirty | 49.74 MiB | 54.81 MiB | 5.07 MiB |
| a5d243c+dirty | 48.30 MiB | 53.54 MiB | 5.23 MiB |
| ecf47a2+dirty | 49.74 MiB | 54.82 MiB | 5.07 MiB |
| a0d8cf8+dirty | 48.30 MiB | 53.49 MiB | 5.19 MiB |
| f170ec3+dirty | 48.30 MiB | 53.57 MiB | 5.26 MiB |
| 9474ead+dirty | 48.30 MiB | 53.61 MiB | 5.30 MiB |
| c151573+dirty | 48.30 MiB | 53.54 MiB | 5.24 MiB |
| 7436d0f+dirty | 48.30 MiB | 53.60 MiB | 5.30 MiB |
| 4e0b819+dirty | 49.74 MiB | 54.81 MiB | 5.07 MiB |
| 5a23c47+dirty | 49.74 MiB | 54.82 MiB | 5.07 MiB |
iOS (new) Performance metrics 🚀
|
| Revision | Plain | With Sentry | Diff |
|---|---|---|---|
| ae37560+dirty | 3840.10 ms | 1217.89 ms | -2622.21 ms |
| e763471+dirty | 3826.10 ms | 1221.52 ms | -2604.58 ms |
| 0b1b5e3+dirty | 3820.72 ms | 1207.94 ms | -2612.78 ms |
| 5ca03f9+dirty | 3873.76 ms | 1236.26 ms | -2637.51 ms |
| acd838e+dirty | 3835.94 ms | 1215.87 ms | -2620.07 ms |
| 5789645+dirty | 3841.36 ms | 1214.81 ms | -2626.55 ms |
| 61cc206+dirty | 3822.60 ms | 1206.17 ms | -2616.43 ms |
| a2585ce+dirty | 3837.98 ms | 1214.20 ms | -2623.77 ms |
| bc8f61e+dirty | 3849.87 ms | 1221.19 ms | -2628.68 ms |
| 5b7e8a7+dirty | 3855.46 ms | 1234.47 ms | -2620.99 ms |
App size
| Revision | Plain | With Sentry | Diff |
|---|---|---|---|
| ae37560+dirty | 5.15 MiB | 6.70 MiB | 1.54 MiB |
| e763471+dirty | 4.98 MiB | 6.51 MiB | 1.53 MiB |
| 0b1b5e3+dirty | 5.15 MiB | 6.70 MiB | 1.54 MiB |
| 5ca03f9+dirty | 4.98 MiB | 6.53 MiB | 1.55 MiB |
| acd838e+dirty | 5.15 MiB | 6.70 MiB | 1.55 MiB |
| 5789645+dirty | 4.98 MiB | 6.50 MiB | 1.52 MiB |
| 61cc206+dirty | 4.98 MiB | 6.55 MiB | 1.57 MiB |
| a2585ce+dirty | 4.98 MiB | 6.55 MiB | 1.57 MiB |
| bc8f61e+dirty | 4.98 MiB | 6.47 MiB | 1.49 MiB |
| 5b7e8a7+dirty | 5.15 MiB | 6.70 MiB | 1.54 MiB |
…avigation-span-6434
…avigation-span-6434
Replace the dispatch-time-only, leaf-name dedup with the more complete two-guard design from #6472, plus a refinement that closes a false-suppression regression both prior approaches shared. - Dispatch-time guard (`isRouteFocused`): skip a `POP_TO` whose target is already focused on the active route chain AND that carries the `withAnchor` marker (`params.initial === false`). Requiring the marker is what lets a genuine `popTo` to an earlier same-named route in the stack (e.g. `[id]` → `[id]`, different `route.key`) through, since those never carry `initial`. - State-change guard: discard a `POP_TO` span that landed on the same `route.key` (a bookkeeping dispatch that slipped past the dispatch-time filter, e.g. a nested payload name), unless a deep link claimed it. Keyed on `route.key`, so it never affects a navigation that changed the route. `applyPendingDeepLinkToSpan` now returns whether it attached, so the state-change guard can preserve deep-link-to-current-screen attribution. Co-authored-by: Cryptoteep <cryptoteep@users.noreply.github.com> Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Preserve deep-link attribution on a same-route POP_TO: check the `taggedDeepLinkSpans` set instead of the just-attached result, so a span tagged earlier via the synchronous late-arrival listener is not discarded (revert `applyPendingDeepLinkToSpan` back to `void`). Reported by cursor bot. - End `navigationProcessingSpan` before dropping it in `_discardLatestTransaction` so the bookkeeping-discard path does not leave an unfinished span dangling. Reported by sentry bot. - Add a test that a same-route POP_TO carrying a deep link is kept. Co-authored-by: Cryptoteep <cryptoteep@users.noreply.github.com> Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit afeba2a. Configure here.
…#6434) The test used `emitNavigationWithoutStateChange()`, whose payload-less NAVIGATE is rejected by the `!dispatchedRouteName` guard under `useDispatchedActionData: true`, so no in-flight span was ever created and the assertion was trivially true. Start a real in-flight navigation via a payloaded NAVIGATE dispatch (no state change) so the test genuinely exercises the focused-route POP_TO not tearing it down. Verified it fails when the dispatch-time guard is removed. Reported by warden bot. Co-authored-by: Cryptoteep <cryptoteep@users.noreply.github.com> Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit a7204aa. Configure here.
…hedActionData (#6434) The two guards were keyed off `navigationActionType` / the span's action-type attribute, both only populated when `useDispatchedActionData` is enabled. That option defaults off for `expoRouterIntegration` and the Expo sample, so the duplicate POP_TO transaction still occurred on the default setup where the bug was reported (confirmed: 2 transactions with the flag off). Drive the dispatch-time guard off `actionType` (parsed unconditionally) and track the latest navigation span's action type in a closure variable for the state-change discard, so both guards work regardless of the flag. The span attribute stays flag-gated to avoid changing trace metadata for existing users. The guards remain narrow: guard 1 requires `params.initial === false` (only set by withAnchor), guard 2 only discards a same-route-key POP_TO (a no-op pop). Add a test asserting the default (flag-off) setup no longer creates the duplicate transaction. Co-authored-by: Cryptoteep <cryptoteep@users.noreply.github.com> Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

📢 Type of change
📜 Description
The fix skips starting a navigation span for a
POP_TOwhose target is the route we are already on💡 Motivation and Context
Fixes #6434
💚 How did you test it?
CI, Manual
📝 Checklist
sendDefaultPIIis enabled.🔮 Next steps