feat(tracing): Correlate deep links with the navigation they trigger#6264
4 issues
Low
`client.on('close')` clears the module-level deep-link listener, breaking tracking after Sentry re-init - `packages/core/src/js/tracing/reactnavigation.ts:350-353`
When the old client closes after a Sentry.init() re-initialization, the registered 'close' handler calls setPendingDeepLinkListener(undefined), wiping the new client's listener — silently disabling cold-start late-arrival attribution for any subsequent navigation. The deeplinkIntegration explicitly guards against this pattern (subscription?.remove() before re-subscribing), but the navigation integration does not.
Deep-link tests clear shared module state only in a trailing statement, risking cascade failures - `packages/core/test/tracing/reactnavigation.test.ts:313`
The new deep-link hand-off tests in reactnavigation.test.ts reset the module-level state in pendingDeepLink.ts (pending, listener, seqCounter) by calling clearPendingDeepLink() as the final statement of each test rather than in beforeEach/afterEach. The beforeEach hook (around line 121) clears Jest mocks and Sentry scopes but does not reset the pending deep-link module. In the normal success path this is harmless because consumePendingDeepLink always nulls pending on consumption, so each test self-cleans before its assertions. However, if an expect(...) throws before consumption completes, the trailing clearPendingDeepLink() is skipped (only the 'drops stale' test uses try/finally). A leaked, still-fresh pending value can then bleed into a later test — notably 'does not attach when no deep link is pending', which relies on the slot being empty — turning one genuine failure into confusing downstream failures. Consider moving clearPendingDeepLink() into beforeEach (alongside the existing scope clears) so cleanup is guaranteed regardless of test outcome. This is a test-hygiene improvement, not a production defect.
`handleLateDeepLink` can tag an unrelated in-flight user navigation with a late cold-start deep link - `packages/core/test/tracing/reactnavigation.test.ts:397-430`
For cold-start links, handleLateDeepLink (reactnavigation.ts:311-314) prefers latestNavigationSpan whenever it is recording, before falling back to initialFinalizedNavSpan. This is correct while the initial navigation is still in flight. But once the initial route has mounted (initialFinalizedNavSpan is set, latestNavigationSpan cleared) and the user dispatches a second, unrelated navigation that is still in flight, latestNavigationSpan now points to that user navigation. If Linking.getInitialURL() resolves at that moment, the cold-start URL is attached to the unrelated user navigation span instead of the initial span. This violates the invariant the code explicitly documents for initialFinalizedNavSpan: "a cold-start link must never retroactively tag a navigation the user performed later." A correct guard would only prefer latestNavigationSpan when initialFinalizedNavSpan has not yet been set (i.e. the initial navigation is still the one in flight).
Cold-start deep link lost when `handleLateDeepLink` tags an in-flight nav span that is later discarded on timeout - `packages/core/test/tracing/reactnavigation.test.ts:576-578`
In handleLateDeepLink (reactnavigation.ts:313), a cold-start deep link that arrives while a navigation span is in-flight (dispatch happened, state change pending) is tagged onto latestNavigationSpan via tagSpanWithDeepLink, which returns true. Because setPendingDeepLink treats a true listener return as 'already attributed' and skips storing the value, the link is never written to the pending slot. If that span is subsequently discarded — e.g. stateChangeTimeout fires _discardLatestTransaction because no state change arrives within routeChangeTimeoutMs — the span is marked for discard and ended, and the deep-link attribution is permanently lost: it is neither on any sent span nor available in the pending slot for the next navigation. This is a graceful-degradation gap in a trace-correlation enhancement, not a security issue, and only triggers in the atypical case where the initial navigation never mounts a route within the timeout.
4 skills analyzed
| Skill | Findings | Duration | Cost |
|---|---|---|---|
| security-review | 0 | 3m 56s | $0.80 |
| code-review | 2 | 11m 37s | $3.18 |
| find-bugs | 2 | 29m 34s | $6.10 |
| gha-security-review | 0 | 1m 43s | $0.11 |
⏱ 46m 49s · 5.5M in / 320.3k out · $10.20