Skip to content

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

Description

@kanzelm3

What React Native libraries do you use?

Expo Router

Are you using sentry.io or on-premise?

sentry.io (SaS)

Are you using any other error monitoring solution alongside Sentry?

No

Other Error Monitoring Solution Name

No response

@sentry/react-native SDK Version

8.16.0

How does your development environment look like?

⬇  Place the `npx react-native@latest info` output here. ⬇

System:
  OS: macOS 26.5.2
  CPU: (16) arm64 Apple M3 Max
  Memory: 5.48 GB / 64.00 GB
  Shell:
    version: "5.9"
    path: /bin/zsh
Binaries:
  Node:
    version: 24.12.0
    path: /Users/joel.kanzelmeyer/.nvm/versions/node/v24.12.0/bin/node
  Yarn:
    version: 4.12.0
    path: /Users/joel.kanzelmeyer/.nvm/versions/node/v24.12.0/bin/yarn
  npm:
    version: 11.6.2
    path: /Users/joel.kanzelmeyer/.nvm/versions/node/v24.12.0/bin/npm
  Watchman:
    version: 2024.10.14.00
    path: /opt/homebrew/bin/watchman
Managers:
  CocoaPods:
    version: 1.15.2
    path: /usr/local/bin/pod
SDKs:
  iOS SDK:
    Platforms:
      - DriverKit 25.2
      - iOS 26.2
      - macOS 26.2
      - tvOS 26.2
      - visionOS 26.2
      - watchOS 26.2
  Android SDK: Not Found
IDEs:
  Android Studio: 2025.3 AI-253.31033.145.2533.15113396
  Xcode:
    version: 26.2/17C52
    path: /usr/bin/xcodebuild
Languages:
  Java:
    version: 17.0.11
    path: /usr/bin/javac
  Ruby:
    version: 2.6.10
    path: /usr/bin/ruby
npmPackages:
  "@react-native-community/cli":
    installed: 20.2.0
    wanted: latest
  react:
    installed: 19.2.0
    wanted: 19.2.0
  react-native:
    installed: 0.83.6
    wanted: 0.83.6
  react-native-macos: Not Found
npmGlobalPackages:
  "*react-native*": Not Found
Android:
  hermesEnabled: true
  newArchEnabled: true
iOS:
  hermesEnabled: true
  newArchEnabled: true


Sentry.init()

Sentry.init({
    // double the default app hang time to 4 seconds
    appHangTimeoutInterval: 4,
    debug: process.env.EXPO_PUBLIC_SENTRY_DEBUG === 'true',
    dsn: process.env.EXPO_PUBLIC_SENTRY_DSN,
    enableAppStartTracking: true,
    // disable in local development
    enabled: isSentryEnabled,
    enableNativeFramesTracking: true,
    enableStallTracking: true,
    enableUserInteractionTracing: true,
    ignoreErrors: ignoredErrors,
    integrations: [
      Sentry.expoRouterIntegration({
        enableTimeToInitialDisplay: true,
        useDispatchedActionData: true,
      }),
      Sentry.reactNativeTracingIntegration(...)
    ],
    // default is 250 char, but some of our errors (e.g. zod) are being truncated
    maxValueLength: 5000,
    profilesSampleRate: isProfilingEnabled ? 1 : undefined,
    sendDefaultPii: true,
    spotlight:
      __DEV__ && process.env.EXPO_PUBLIC_ENABLE_SENTRY_LOCALLY === 'true',
});

Steps to Reproduce

  1. Initialize Sentry with expoRouterIntegration({ useDispatchedActionData: true }).
  2. In an Expo Router app, trigger any navigation that passes withAnchor: true, e.g.: router.dismissTo('/screenB', { withAnchor: true })
  3. Enable debug logging (Sentry.init({ debug: true })) and watch the console, or inspect the resulting traces in Sentry.
  4. Note that withAnchor: true causes Expo Router to issue a second dispatch after the real navigation, purely to stamp initial: false onto the destination route (see expo-router's routing.js: currentParams.initial = !withAnchor, only ever set inside if (withAnchor)). This second dispatch is a POP_TO whose payload looks like: { type: 'POP_TO', payload: { name: '<destination route>', params: { initial: false } } }
  5. Observe the transactions/spans produced for this single user-facing navigation.

Expected Result

A single navigation transaction is created and named for the real destination route (/screenB).

Actual Result

Two navigation transactions are created. The first is the real navigation, correctly named. The second is a spurious duplicate transaction for the POP_TO bookkeeping dispatch — startIdleNavigationSpan runs unconditionally on every '__unsafe_action__' dispatch, and POP_TO isn't in the list of action types filtered out when useDispatchedActionData is enabled (PRELOAD, SET_PARAMS, OPEN_DRAWER, CLOSE_DRAWER, TOGGLE_DRAWERreactnavigation.ts, startIdleNavigationSpan). This second transaction carries no real work of its own, but if the destination screen is still loading data when this bookkeeping dispatch fires, any in-flight spans (HTTP requests, TTID/TTFD) can get attached to it instead of the first (real) transaction, and it lingers until childSpanTimeout (15s default) force-closes it, showing up in Sentry as an extra, confusing transaction — sometimes named generically ("Route Change") if the route name never resolves cleanly, sometimes duplicating the real destination's name, with several of its child spans forced into a cancelled status when the idle span is torn down.

Metadata

Metadata

Assignees

No one assigned

    Projects

    Status
    No status

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions