feat(tracing): Correlate deep links with the navigation they trigger#6264
1 issue
find-bugs: Found 2 issues (2 low)
Low
`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.
⏱ 29m 34s · 2.5M in / 219.2k out · $6.10