Skip to content

fix(tracing): don't create a duplicate navigation span for Expo Router's withAnchor POP_TO bookkeeping dispatch#6472

Open
Cryptoteep wants to merge 2 commits into
getsentry:mainfrom
Cryptoteep:fix/expo-router-withanchor-popto-duplicate-span
Open

fix(tracing): don't create a duplicate navigation span for Expo Router's withAnchor POP_TO bookkeeping dispatch#6472
Cryptoteep wants to merge 2 commits into
getsentry:mainfrom
Cryptoteep:fix/expo-router-withanchor-popto-duplicate-span

Conversation

@Cryptoteep

Copy link
Copy Markdown
Contributor

📢 Type of change

  • Bugfix
  • New feature
  • Enhancement
  • Refactoring

📜 Description

Expo Router issues a second POP_TO dispatch after a navigation with withAnchor: true, purely to stamp initial: false onto the destination it just navigated to (expo-router getNavigationAction: currentParams.initial = !withAnchor). With useDispatchedActionData: true this bookkeeping dispatch started a second idle navigation span for a single user-facing navigation. The spurious span stole in-flight child spans (HTTP, TTID/TTFD) from the real navigation, lingered until childSpanTimeout force-closed it, and shipped as a confusing duplicate transaction with several children in cancelled status.

Simply adding POP_TO to the filtered action list would break tracking of genuine popTo/dismissTo navigations (on React Navigation 7, dismissTo is a POP_TO). Instead this adds two scoped guards, both active only with useDispatchedActionData:

  1. Dispatch time — skip a POP_TO whose target route is already focused somewhere on the active route chain (root → leaf, reusing the same traversal as getPathFromState). A genuine popTo targets a route behind the focused one, and __unsafe_action__ fires before state changes are applied (documented in React Navigation's types), so its target is never focused at that point. This also protects the real navigation's in-flight span from being discarded by the "noop transaction" branch when the bookkeeping dispatch arrives before the deferred state-change microtask (reactNavigationIntegration: navigation transaction can be named/attributed for the previous route instead of the destination when using a route override provider (e.g. expoRouterIntegration) #6436).
  2. State-change time — a POP_TO span that landed on the same route key (the dispatch-time filter can miss when the payload carries a parent navigator name that matches no focused route) is now discarded instead of being abandoned to run out its idle timeout — unless a pending deep link claimed it, preserving the deep-link-to-current-screen attribution added earlier.

No behavior change when useDispatchedActionData is disabled (default): the action-type attribute is never set there, so both guards are inert.

💡 Motivation and Context

Fixes #6434

💚 How did you test it?

Added 7 unit tests covering: a genuine POP_TO to a different route still creates a named span; the bookkeeping POP_TO (leaf and nested/parent-navigator payload names) creates no span; it does not remove an in-flight navigation span from the scope; a same-route-key POP_TO span is discarded unless a deep link claimed it; and NAVIGATE landing on the same route keeps its current behavior. Full suite: yarn jest test/tracing/reactnavigation.test.ts — 121 passed, plus pendingDeepLink/expoRouterIntegration/idleNavigationSpan/ttid suites green. tsc -p tsconfig.build.json and oxlint/oxfmt clean.

📝 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.
  • Public API changes reviewed by another Mobile SDK team member or implemented according to the develop docs spec.
  • No breaking changes.

🔮 Next steps

If preferred, the same-route-key discard (guard 2) could later be generalized beyond POP_TO, but that would change behavior for same-route NAVIGATE params updates, so it's deliberately left untouched here.

…r's withAnchor POP_TO bookkeeping dispatch

Expo Router issues a second POP_TO dispatch after a navigation with
withAnchor: true, purely to stamp 'initial: false' onto the destination
it just navigated to. With useDispatchedActionData enabled this dispatch
started a second idle navigation span that stole in-flight child spans
(HTTP, TTID/TTFD) from the real navigation and shipped as a spurious
duplicate transaction.

Two guards, both scoped to useDispatchedActionData:

- At dispatch time, skip POP_TO actions whose target route is already
  focused somewhere on the active route chain. A genuine popTo targets a
  route behind the focused one and __unsafe_action__ fires before the
  state change is applied, so real navigations are unaffected.
- At state-change time, discard (rather than abandon) a POP_TO span that
  landed on the same route key, unless a pending deep link claimed it.

Fixes getsentry#6434

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

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): don't create a duplicate navigation span for Expo Router's withAnchor POP_TO bookkeeping dispatch by Cryptoteep in #6472

🤖 This preview updates automatically when you update the PR.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

withAnchor's internal POP_TO bookkeeping dispatch creates a spurious duplicate navigation span

2 participants