feat(tracing): Wrap Expo Router push, replace, navigate, back, dismiss in addition to prefetch#6221
5 issues
Medium
Stale `pendingExpoRouterNavigation` leaks to the next unrelated navigation span when `back()`/`dismiss()` is a no-op - `packages/core/src/js/tracing/expoRouter.ts:170-176`
When back() is called on an empty navigation stack (or dismiss() with nothing to dismiss), setPendingExpoRouterNavigation stores a pending value but React Navigation never fires a navigation action, so consumePendingExpoRouterNavigation in startIdleNavigationSpan is never called — the stale entry persists until the next unrelated navigation fires, incorrectly attributing its span with navigation.method: 'back'. Consider calling clearPendingExpoRouterNavigation() after a short timeout, or after confirming the navigation event fired.
Low
dismiss test omits `consumePendingExpoRouterNavigation` assertion present in the parallel `back` test - `packages/core/test/tracing/expoRouter.test.ts:424-444`
The dismiss test verifies the span and breadcrumb but never asserts that consumePendingExpoRouterNavigation() returns { method: 'dismiss' }, so a regression in setPendingExpoRouterNavigation for dismiss would go undetected.
Stale pending navigation value can contaminate app-restart and initial-setup spans - `packages/core/src/js/tracing/pendingExpoRouterNavigation.ts:22`
The module-level pending singleton has no expiry, so a value set by a router call that never produces a __unsafe_action__ event (e.g. navigation attempted before the container is registered) will be consumed and applied as navigation.method by the next startIdleNavigationSpan invocation regardless of whether it is a normal dispatch, an initial setup call, or an isAppRestart=true call.
Test asserts router is 'unchanged' but push method is now silently wrapped - `packages/core/test/tracing/expoRouter.test.ts:54-57`
The test at line 51 passes { push: jest.fn() } (no prefetch) and asserts expect(wrapped).toBe(router), but wrapExpoRouter now replaces router.push with a wrapped version — the assertion only validates object identity, not method identity, so the test stays green while the described invariant is violated.
Test 'returns the router unchanged if prefetch method does not exist' no longer holds: push is silently wrapped
The test asserts wrapped === router (trivially true), but after this PR router.push is replaced by a wrapper function and router.__sentryWrapped is added — the router is mutated. The test's assertion gives false confidence that no wrapping occurs when prefetch is absent.
4 skills analyzed
| Skill | Findings | Duration | Cost |
|---|---|---|---|
| security-review | 0 | 2m 14s | $0.41 |
| code-review | 2 | 6m 17s | $2.39 |
| find-bugs | 3 | 25m 9s | $5.53 |
| gha-security-review | 0 | 13.1s | $0.14 |
⏱ 33m 53s · 3.6M in / 270.9k out · $8.46