Skip to content

feat(tracing): Wrap Expo Router push, replace, navigate, back, dismiss in addition to prefetch#6221

Merged
alwx merged 11 commits into
mainfrom
alwx/feature/wrap-expo-router-methods
Jun 3, 2026
Merged

feat(tracing): Wrap Expo Router push, replace, navigate, back, dismiss in addition to prefetch#6221
alwx merged 11 commits into
mainfrom
alwx/feature/wrap-expo-router-methods

fix(tracing): Drain pendingExpoRouterNavigation even when the idle-sp…

499ab39
Select commit
Loading
Failed to load commit list.
@sentry/warden / warden completed Jun 2, 2026 in 25m 51s

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