Repository navigation
[$175] [Sentry: APP-2AP] RNSNavigationController pushes same screen instance twice #102743
Description
Activity
- addedExternalAdded to denote the issue can be worked on by a contributorAdded to denote the issue can be worked on by a contributorDailyKSv2KSv2BugSomething is broken. Auto assigns a BugZero manager.Something is broken. Auto assigns a BugZero manager.
on Oct 1, 2026 Triggered auto assignment to Contributor-plus team member for initial proposal review - @mkhutornyi (
External)- changed the title
[-][Sentry: APP-2AP] RNSNavigationController pushes same screen instance twice[/-][+][$175] [Sentry: APP-2AP] RNSNavigationController pushes same screen instance twice[/+]on Oct 1, 2026 Job added to Upwork: https://www.upwork.com/jobs/~022105599577912259397
- added a parent issue
on Oct 1, 2026 Proposal
⚠️ Confidence: medium. The native code path is confirmed. The exact user action that triggers it is not, because I couldn't load the Sentry stack trace for APP-2AP. No repro is known.What is the root cause of that problem?
This is a bug in
react-native-screens. It happens when two stack updates arrive before UIKit has run a push.setPushViewControllershas the only push call in the stack:RNSScreenStack.mm:657-665. It decides whether to push by checking two things:_controller.viewControllersdoes not already contain the screen.transitionCoordinatoris nil (:597-611).
UIKit can accept a push and run it after the next Core Animation commit. Until then, the screen is not in
viewControllersandtransitionCoordinatoris nil, so both checks pass. A secondupdateContainerthen callspushViewControllerorsetViewControllersagain with the sameRNSScreen. When UIKit runs the deferred push, it throwsNSInvalidArgumentException.Several things can call
updateContainerback to back:- one
dispatch_asyncper Fabric mounting transaction didMoveToWindowwhen a bottom tab is attachedmarkChildUpdated- the transition completion block
In the App, any of these can cause two updates in a row:
linkTodispatching two actions- a tab switch combined with a push
- a notification tap or deep link
Every App navigator except the tab navigators renders
NativeStackViewon iOS (createPlatformStackNavigatorComponent/index.native.tsx:99).Upstream has not fixed this.
4.28.0is the latest 4.x release, and upstreammainhas the same push logic with no guard against a pending push. The older upstream report RNScreensNavigationController is pushing the same view controller instance more than once was closed in 2021. Users kept reporting the same crash there through 4.4.0, with the same_UIAfterCACommitBlock→pushViewController:transition:forceImmediate:stack.The App has no patch for
react-native-screens, so a local patch did not cause this.What changes do you think we should make in order to solve the problem?
Patch
react-native-screenswithpatch-package, and send the same fix upstream:- In
RNSScreenStackView, remember the screen being pushed whenpushViewController:topis called at:665. Clear it in the existingnavigationController:didShowViewController:delegate method (:988). - At the start of
setPushViewControllers, if a push is still pending, don't change the stack. Schedule oneupdateContainerfor after the push finishes, using the same_updateScheduledpattern the method already uses fortransitionCoordinator. - Before calling
pushViewController, also skip the push iftop.parentViewController == _controller. - Document the patch in
patches/react-native-screens/details.md, and link the upstream issue or PR.
The next update then applies the final state once, so the user ends up on the correct screen and the app doesn't crash.
What alternative solutions did you explore? (Optional)
- Debounce navigation in JS (
Navigation.tsorlinkTo). I rejected this. Many native paths callupdateContainerwithout a JS navigation call (tab attach, mount transactions), so it would not cover every trigger. It would also slow down navigation everywhere. - Look at
usePreventRemoveinScreenWrapper(ScreenWrapper/index.tsx:204-210). This turns on RNS'spreventNativeDismissflow, which pushes the sameRNSScreenagain on purpose after a native dismiss is blocked. It is a possible second trigger. The native guard above covers it too.
What would raise confidence
Check the Sentry APP-2AP events for:
- The frames below
-[RNSScreenStackView setPushViewControllers:]. They show which caller made the second update: the mountdispatch_async,didMoveToWindow,viewDidDisappear(preventNativeDismiss), or the transition completion. _UIAfterCACommitBlock/_runAfterCACommitDeferredBlocksin the stack. Seeing them confirms the deferred-push mechanism.- Breadcrumbs showing two navigation actions within about one frame, for example after a notification tap, a deep link, or a return from OldDot.
Next Steps for Contributor+ team:
To accept:@MelvinBot implement [this](https://github.com/Expensify/App/issues/102743)to create a draft PR.
To refine:@MelvinBot <your feedback>
To reject: Explain why you are rejecting Melvin's proposal.
@mountiny this looks like a good candidate for SWM navigation team?
Proposal
Please re-state the problem that we are trying to solve in this issue.
On iOS (HybridApp), the app crashes with an
NSInvalidArgumentExceptionstatingRNSNavigationController is pushing the same view controller instance (RNSScreen) more than oncewhen rapid screen pushes or state updates occur before UIKit completes the initial push animation.What is the root cause of that problem?
In
react-native-screens(RNSScreenStack.mm:657–665),setPushViewControllerschecks if a screen is already in_controller.viewControllersand whethertransitionCoordinatorisnil.When rapid navigation dispatches (e.g. double taps, deep links, tab switches) happen before the Core Animation commit finishes, the screen is not yet present in
viewControllers, passing both checks and causingpushViewControllerto be called a second time with the exact sameRNSScreeninstance.What changes do you think we should make in order to solve the problem?
Apply a patch to
react-native-screensusingpatch-package(and add documentation topatches/react-native-screens/details.md):- Track the pending screen instance during
pushViewController:topinRNSScreenStackView. - Clear the pending push tracker inside the delegate callback
navigationController:didShowViewController:. - At the beginning of
setPushViewControllers, if a push operation is currently pending ortop.parentViewController == _controller, skip the immediate push and queue a single_updateScheduledrun for after the transition completes.
What alternative solutions did you consider?
Debouncing navigation actions in JS (
src/libs/Navigation/Navigation.ts), but this was rejected because it doesn't prevent native mounting transactions or tab attachment dispatches from triggering duplicate pushes on the native side.Contributor details
Your Expensify account email: jacobtrevino444@gmail.com
Upwork Profile Link: https://www.upwork.com/freelancers/~01cc432d2b667fd0ff- Track the pending screen instance during
✅ Contributor details stored successfully. Thank you for contributing to Expensify!
Hey, I'll take a look at it!
Reacted by mkhutornyiI'm still investigating it 🕵️ I'm checking if it's possible to create an application that reproduces the issue. If so, I'll report it and try to fix it upstream
I built an app that reproduces this issue https://github.com/WojtekBoman/rns-ios17-push-same-vc-twice-repro. I sent it to the
react-native-screensteam and am waiting for their feedback on whether this should be fixed directly in the library 🫡I created a draft PR: #103603. I discussed this issue with the
react-native-screensmaintainers and we agreed that a patch is a good solution for now. The bug should be fixed properly in RNS 5.0, at which point we can remove the patch 🫡It should be ready for review early next week!
Reacted by mkhutornyi
Metadata
Metadata
Assignees
Labels
Type
Projects
- StatusShow more project fieldsNo status
Sentry
https://expensify.sentry.io/issues/APP-2AP
Impact (snapshot at filing)
Stack trace (top frames, first-party only)
Error:
NSInvalidArgumentException: RNSNavigationController is pushing the same view controller instance (RNSScreen) more than onceSuspected cause
react-native-screens pushes the same screen instance twice, likely from a duplicate navigation action firing before the first push animation completes.
Reproduction
Unknown — see Sentry events linked from the Sentry issue.
Related
Upwork Automation - Do Not Edit
Issue Owner
Current Issue Owner: @WojtekBoman