fix(react-router): Do not re-write origin on router state changes#21056
Merged
Conversation
Closes #20784 The router state-change subscription wrote `sentry.origin: auto.navigation.react_router` to whatever root span happened to be active. When the route was static (so the parameterized path equals the URL pathname), the `pathname === rootSpanName` guard did not filter out the still-active pageload span — so the pageload got tagged with the navigation origin, producing transactions with `op=pageload` but `origin=auto.navigation.react_router`. Dynamic routes hid the bug because the parameterized name no longer equalled the pathname, short-circuiting the block. The origin override here was also incorrect for the instrumentation API path: that creates the navigation span with `auto.navigation.react_router.instrumentation_api`, and the subscribe callback was stripping the `.instrumentation_api` suffix on its way through. The origin is correctly set once at span creation (or once in `trySubscribe` for the pageload). The subscribe callback only needs to update name + source. Drop the origin write entirely. Added a regression test asserting the pageload's origin is preserved when the subscribe callback fires while it's still active, and tightened the existing navigation assertion to check the attribute payload rather than just that `setAttributes` was called. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 2 potential issues.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit de12513. Configure here.
… 'route') Source now uses the single-attribute Span#setAttribute API since only `source` is updated by the router.subscribe callback. The test mocks need a setAttribute spy, and the regression assertion has to match the new call shape. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… for true origin These tests were asserting `origin: auto.navigation.react_router`, but that legacy origin was only being produced because of a bug in the router state-change subscription that unconditionally overwrote the origin attribute. With `useInstrumentationAPI: true` and the instrumentation array passed to HydratedRouter, React Router invokes the navigate hook on the client and the navigation span is genuinely created via the instrumentation API — so the correct origin is `auto.navigation.react_router.instrumentation_api`. The subscribe callback still updates the span name to its parameterized form so `sentry.source` ends up as `route`. Renamed the describe block and updated the top-of-file comment to reflect the new understanding. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
nicohrubec
approved these changes
May 20, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

Summary
Closes #20784. Closes #20772.
The router state-change subscription in
hydratedRouter.tswas unconditionally re-writingsentry.originon whatever root span was active at the time. Two side effects:pathname === rootSpanNameguard didn't filter out the still-active pageload span, so the pageload got tagged withauto.navigation.react_routerand produced transactions withop=pageloadbutorigin=auto.navigation.react_router. Dynamic routes hid the bug because the parameterized name no longer equalled the pathname, short-circuiting the block.createClientInstrumentation, its origin isauto.navigation.react_router.instrumentation_api. The subscribe callback's re-write was stripping the.instrumentation_apisuffix.The origin is correctly set once at span creation (in
maybeCreateNavigationTransaction/ instrumentation API) or once intrySubscribefor the pageload. The subscribe callback only needs to updatename+source. Drop the origin write entirely.Added a regression test asserting the pageload origin is preserved when the subscribe callback fires while the pageload is still the active root, and tightened the existing navigation assertion to check the attribute payload rather than just that
setAttributeswas called.Also corrected the
react-router-7-framework-instrumentationnavigation e2e tests, which were asserting the legacy origin only because of the same bug — React Router 7 actually invokes the instrumentation API navigate hook on the client, so the navigation spans genuinely carry the.instrumentation_apiorigin (with the legacy subscribe callback still doing parameterization).