Repository navigation
[Due for payment 2026-10-21] [$175] [Sentry: APP-M0V] Android crash, IllegalStateException Fragment already added in ScreenStackFragment #102046
Description
If you haven’t already, check out our contributing guidelines for onboarding. To join our Slack channel, fill out this form.
Version Number: 9.4.89
Reproducible in staging?: Needs to be confirmed
Reproducible in production?: Yes — crash is live in production
If this was caught during regression testing, add the test name, ID and link from BrowserStack: N/A — found via Sentry production crash scan
Email or phone of affected tester (no customers): N/A
Logs: https://stackoverflow.com/c/expensify/questions/4856
Expensify/Expensify Issue URL:
Issue reported by: Sentry production crash scan
Slack conversation (hyperlinked to channel name): N/A
Action Performed:
- Open the Expensify app on Android (HybridApp).
- Trigger a screen that presents a modal over a navigation transition. The reported view is the AI features promo modal, shown shortly after the app starts.
- Navigate while that modal is appearing or dismissing.
Expected Result:
The modal and the navigation transition both complete. No crash.
Actual Result:
The app crashes on the main thread:
IllegalStateException: Fragment already added: ScreenStackFragment{...}
react-native-screens runs a fragment transaction that adds a fragment already present in the fragment store.
Workaround:
Unknown. The app restarts.
Platforms:
Select the officially supported platforms where the issue was reproduced:
- Android: App
- Android: mWeb Chrome
- iOS: App
- iOS: mWeb Safari
- iOS: mWeb Chrome
- Windows: Chrome
- MacOS: Chrome / Safari
Screenshots/Videos
Sentry details
Sentry issue: https://expensify.sentry.io/issues/APP-M0V
Impact (snapshot at filing, 2026-09-23):
- Users (last 7d): 12
- Events (last 7d): 58
- First seen: 2026-09-16
- Last seen: ongoing
- Platform: Android native, HybridApp (
org.me.mobiexpensifyg), Android 16, Samsung flagship hardware - View at crash time:
Dynamic_AIFeaturesPromoModal_Root - Mechanism:
UncaughtExceptionHandler, levelfatal - Substatus: escalating
Stack trace (first-party and library frames, innermost last):
com.facebook.react.bridge.queue.MessageQueueThreadHandler.dispatchMessage(MessageQueueThreadHandler.kt:21)
com.swmansion.rnscreens.ScreenContainer.onScreenChanged$lambda$8(ScreenContainer.kt:358)
com.swmansion.rnscreens.ScreenContainer.performUpdates(ScreenContainer.kt:375)
com.swmansion.rnscreens.ScreenStack.onUpdate(ScreenStack.kt:303)
androidx.fragment.app.BackStackRecord.commitNowAllowingStateLoss(BackStackRecord.java:323)
androidx.fragment.app.FragmentManager.execSingleAction(FragmentManager.java:2002)
androidx.fragment.app.FragmentManager.addFragment(FragmentManager.java:1733)
androidx.fragment.app.FragmentStore.addFragment(FragmentStore.java:93)
Suspected cause: a race between a modal presentation and a navigation update. ScreenStack.onUpdate computes the set of fragments to attach, then performUpdates commits the transaction on the next handler tick. If a second update lands in between, the same ScreenStackFragment gets queued for addition twice, and FragmentStore.addFragment throws.
Worth checking the react-native-screens version in use against upstream fixes for duplicate fragment attachment, and whether the AI features promo modal mounts during a navigation transition rather than after it settles.
Issue Owner
Current Issue Owner: @nyomanjyotisaUpwork Automation - Do Not Edit
- Upwork Job URL: https://www.upwork.com/jobs/~022109300479659342960
- Upwork Job ID: 2109300479659342960
- Last Price Increase: 2026-10-11
Metadata
Metadata
Assignees
Labels
Type
Projects
- StatusShow more project fieldsNo status
Activity
melvin-bot commentedon Sep 23, 2026
Triggered auto assignment to Contributor-plus team member for initial proposal review - @nyomanjyotisa (
External)melvin-bot commentedon Sep 23, 2026
Unable to auto-create job on Upwork. The BZ team member should create it manually for this issue.
MelvinBot commentedon Sep 23, 2026
Proposal
What is the root cause of that problem?
A missing
isAddedguard inreact-native-screens@4.25.0, on the Android code path that only runs when the top screen is translucent. The AI features promo modal is exactly that kind of screen, which is why it dominates this Sentry signal.In
ScreenStack.onUpdate, when the screen underneath a translucent screen is not attached, every fragment fromvisibleBottomupward is added to the transaction with no check of whether it is already attached:So if a navigation update detaches the screen below the modal while the modal's own fragment stays attached,
transaction.addis called on an already-addedScreenStackFragment, andFragmentStore.addFragmentthrows on commit. The siblingnewTopbranch two lines below guards correctly — the asymmetry is the bug.visibleBottomis non-null only when the top screen is translucent, andScreen.isTranslucent()is true fortransparentModal. The AI promo modal is registered withrootNavigatorScreenOptions.centeredModalNavigator, which setspresentation: Presentation.TRANSPARENT_MODAL.This is a known upstream bug: Crash on Android: "Fragment already added" in ScreenStack.onUpdate with transparentModal after background/resume, with an open, unmerged fix in fix(Android): guard against re-adding an attached fragment in ScreenStack.onUpdate.
Evidence: stack trace lines map exactly onto the installed 4.25.0 source
Every frame in the Sentry trace lines up with the code we actually ship (
react-native-screens": "4.25.0"inpackage.json:217):ScreenContainer.onScreenChanged$lambda$8(ScreenContainer.kt:358)performUpdates()insiderunOnUiQueueThreadScreenContainer.performUpdates(ScreenContainer.kt:375)onUpdate()ScreenStack.onUpdate(ScreenStack.kt:303)transaction.commitNowAllowingStateLoss()— commits the transaction built at L267–L283The modal's fragment is never removed earlier in that same transaction: the two
transaction.removepasses cover wrappers that leftscreenWrappers/were dismissed, and wrappers belowvisibleBottom. The modal sits abovevisibleBottom, so it reachesaddstill attached.The upstream reporter on #4156 hit the same crash from the Paper
ScreensShadowNodeentry point; we hit it from the FabricrunOnUiQueueThreadentry point. Same crash site, same missing guard. Their device profile (Samsung / Pixel, Android 16) matches ours.Note #4445 in the same repo is a different crash —
ScreenFragmentinScreenContainer.onUpdate, caused by a deferred-commit workaround that App does not have.What changes do you think we should make in order to solve the problem?
Add a
patch-packagepatch toreact-native-screens@4.25.0that mirrors upstream PR #4157: in the translucent re-attach branch ofScreenStack.onUpdate, filter the sequence to skip wrappers whose fragment is already added, exempting dismissed wrappers (theirremoveis queued earlier in the same transaction, so remove-then-add is the legal re-attach path).Follow the existing convention in
patches/react-native-screens/:react-native-screens+4.25.0+002+fix-fragment-already-added-in-screenstack-onupdate.patch.details.mdentry recording the reason, the upstream PR, this issue, and the removal condition — drop the patch once we bump to the first release containing the merged Add modal for existing owners error #4157.Because the crash needs a real device race, validate by checking that the Sentry event rate for APP-M0V goes to zero after the build ships, rather than expecting a local repro.
What alternative solutions did you explore? (Optional)
react-native-screensto 4.28.0. I checked the 4.28.0 source — this does not fix it. 4.28.0 added a filter to that loop, but it only excludes dismissed andINACTIVEwrappers. An attached, active translucent fragment still passes through totransaction.addand still throws. Add modal for existing owners error #4157 is unmerged, so no published release carries the guard.transparentModalfor the promo modal. Would sidestep the branch for one screen but leave every othercenteredModalNavigator/basicModalNavigatorscreen exposed, and it changes product behaviour to work around a library bug.useAIFeaturesPromoModalalready defers behindTransitionTracker.runAfterTransitions({waitForUpcomingTransition: true})andwaitForProtectedRoutes().TransitionTrackertracks JS-side transition events, not Android fragment-transaction state, so more deferral would only shift the odds — it cannot close the race.Related issues
I found no duplicate or related open issue in
Expensify/Appfor this crash.Note: I could not verify this in a browser — browser control was unavailable for this run, and this is an Android-native crash that a web session could not reproduce anyway. The analysis is from static analysis of the shipped
react-native-screens@4.25.0source, the Sentry stack trace, and upstream issue reports.Next Steps for Contributor+ team:
To accept:
@MelvinBot implement [this](https://github.com/Expensify/App/issues/102046)to create a draft PR.To refine:
@MelvinBot <your feedback>To reject: Explain why you are rejecting Melvin's proposal.
view run
nyomanjyotisa commentedon Sep 28, 2026
@MelvinBot The guard stops the crash but hides the open modal in my isolated Android repro. Removing and re-adding attached, non-dismissed fragments preserved it. Could you update the proposal?
MelvinBot commentedon Sep 28, 2026
Proposal (updated)
Updated 2026-09-28: Corrected based on feedback from
nyomanjyotisa. The fix now removes and re-adds attached fragments instead of skipping them.What is the root cause of that problem?
The crash cause is the same. In
react-native-screens@4.25.0, the translucent re-attach branch ofScreenStack.onUpdatecallstransaction.addfor every screen fromvisibleBottomupward. It never checksisAdded. When a navigation update detaches the screen under the AI promo modal (atransparentModal), the modal's fragment is still attached. Adding it again throwsFragment already added.Why skipping the add hides the modal: the stack's drawing order depends on the order in which fragments are added.
FragmentManagerappends each added fragment's view to the end ofScreenStack's children.ScreenStackdraws its children in child order.visibleBottomgets appended after it and draws on top of it.runOnCommit { top?.screen?.bringToFront() }call does not fix the order.screenis a child of the fragment'sScreensCoordinatorLayout(ScreenStackFragment.kt:207), not ofScreenStack. It only moves the screen inside that wrapper.What changes do you think we should make in order to solve the problem?
Patch
react-native-screens@4.25.0withpatch-package. In the translucent re-attach loop, queue aremovebefore theaddfor any fragment that is already attached and not dismissed:.forEach { wrapper -> if (wrapper.fragment.isAdded && !dismissedWrappers.contains(wrapper)) { transaction.remove(wrapper.fragment) } transaction.add(id, wrapper.fragment).runOnCommit { top?.screen?.bringToFront() } }This fixes both problems:
visibleBottomupward is added again in stack order, so the modal's view ends up last and draws on top.Dismissed wrappers skip the extra
removebecause the first pass in the same transaction already removes them. The remove-then-add pattern is the same re-attach path those wrappers use today.Follow the conventions in
patches/react-native-screens/:react-native-screens+4.25.0+002+fix-fragment-already-added-in-screenstack-onupdate.patch.details.mdentry with the reason, this issue, and a link to upstream #4157. Note that its skip-only guard causes the hidden-modal regression. Remove our patch once upstream ships a fix that keeps the draw order.Verification:
What alternative solutions did you explore? (Optional)
visibleBottom, as your repro showed.react-native-screens@4.28.0. Its added filter only excludes dismissed andINACTIVEwrappers. An attached, active modal still gets added twice.transparentModalfor the promo modal. This protects only one screen and changes product behavior to work around a library bug.Next Steps for Contributor+ team:
To accept:
@MelvinBot implement [this](https://github.com/Expensify/App/issues/102046)to create a draft PR.To refine:
@MelvinBot <your feedback>To reject: Explain why you are rejecting Melvin's proposal.
view run
nyomanjyotisa commentedon Sep 29, 2026
@MelvinBot The remove/re-add fix prevented the crash and kept the modal visible in my isolated 4.25.0 Android test. App now uses 4.28.0. Could you update the proposal for that version?
MelvinBot commentedon Sep 29, 2026
Proposal (updated for
react-native-screens@4.28.0)Updated 2026-09-29: Retargeted to 4.28.0, based on feedback from
nyomanjyotisa. The fix is the same remove/re-add approach you validated on 4.25.0.What is the root cause of that problem?
4.28.0 still has the bug. In the translucent re-attach loop of
ScreenStack.onUpdate, every wrapper fromvisibleBottomupward goes totransaction.addwithout anisAddedcheck. When the screen under the AI promo modal (atransparentModal) is detached but the modal stays attached, the modal is added twice. The commit then throwsFragment already added.4.28.0 only added a filter for dismissed and
INACTIVEwrappers (upstream #4571). An attached, active modal still passes the filter. The upstream skip-only fix, fix(Android): guard against re-adding an attached fragment in ScreenStack.onUpdate, is still open, and no release after 4.28.0 includes a fix.What changes do you think we should make in order to solve the problem?
Add a
patch-packagepatch for 4.28.0. In that loop, remove an attached fragment before adding it again:.filter { !dismissedWrappers.contains(it) && it.screen.activityState !== Screen.ActivityState.INACTIVE } .forEach { wrapper -> // Remove and re-add attached fragments so they're re-appended in stack order and the translucent top stays on top. if (wrapper.fragment.isAdded) { transaction.remove(wrapper.fragment) } // TODO: It should be enough to dispatch this on commit action once. transaction.add(id, wrapper.fragment).runOnCommit { top?.screen?.bringToFront() } }The change from the 4.25.0 version: the
!dismissedWrappers.contains(wrapper)check is gone. 4.28.0 already filters dismissed wrappers out of this loop, so the check can never be false. No wrapper in this loop is removed earlier in the same transaction, so nothing gets removed twice.The 4.28.0 bump in Bump react-native-screens to 4.28.0 and drop the patch deleted
patches/react-native-screens/. So:patches/react-native-screens/react-native-screens+4.28.0+001+fix-fragment-already-added-in-screenstack-onupdate.patch.patches/react-native-screens/details.mdin the standard format (seepatches/react-native-reanimated/details.md). Link this issue and upstream Adding a space before the search term is giving totally different results #4156 / Add modal for existing owners error #4157. Note that Add modal for existing owners error #4157's skip-only guard hides the modal. Remove the patch once upstream ships a fix that keeps the draw order.Verification
I did not run this on a device. The crash needs a native timing race, and the tester can't apply
patch-packagepatches. The code above is based on the 4.28.0 source at the release tag.What alternative solutions did you explore? (Optional)
5.0.0-alpha.3. Neither contains a fix.Next Steps for Contributor+ team:
To accept:
@MelvinBot implement [this](https://github.com/Expensify/App/issues/102046)to create a draft PR.To refine:
@MelvinBot <your feedback>To reject: Explain why you are rejecting Melvin's proposal.
view run
mountiny commentedon Oct 1, 2026
@nyomanjyotisa how is this looking?
5 remaining items
melvin-bot commentedon Oct 5, 2026
Current assignee @mountiny is eligible for the choreEngineerContributorManagement assigner, not assigning anyone new.
mountiny commentedon Oct 5, 2026
@nyomanjyotisa please go ahead with the proposal from Melvin then
nyomanjyotisa commentedon Oct 6, 2026
@MelvinBot please implement your revised proposal.
MelvinBot commentedon Oct 6, 2026
🤖 I created a draft PR: Patch react-native-screens to fix Fragment already added crash on Android
Please mark it as "Ready for review" when it is ready for review.
It implements the revised proposal for
react-native-screens@4.28.0. The PR adds two files:patches/react-native-screens/react-native-screens+4.28.0+001+fix-fragment-already-added-in-screenstack-onupdate.patch: in the translucent re-attach loop ofScreenStack.onUpdate, it removes an already attached fragment before adding it again.patches/react-native-screens/details.md: links this issue and upstream Adding a space before the search term is giving totally different results #4156 / Add modal for existing owners error #4157, and says when to remove the patch.Checks
ScreenStack.kt. The installednode_modulescopy matches that tag byte for byte.details.md, which is the checkvalidatePatches.shruns.nyomanjyotisa, please fill out theTestsandQAsections from your Android before/after test.view run
[-][Sentry: APP-M0V] Android crash, IllegalStateException Fragment already added in ScreenStackFragment[/-][+][$175] [Sentry: APP-M0V] Android crash, IllegalStateException Fragment already added in ScreenStackFragment[/+]melvin-bot commentedon Oct 11, 2026
Job added to Upwork: https://www.upwork.com/jobs/~022109300479659342960
[-][$175] [Sentry: APP-M0V] Android crash, IllegalStateException Fragment already added in ScreenStackFragment[/-][+][Due for payment 2026-10-21] [$175] [Sentry: APP-M0V] Android crash, IllegalStateException Fragment already added in ScreenStackFragment[/+]melvin-bot commentedon Oct 14, 2026
@nyomanjyotisa
The solution for this issue has been 🚀 deployed to production 🚀 in version 9.5.8-5 and is now subject to a 7-day regression period 📆. Here is the list of pull requests that resolve this issue:
If no regressions arise, payment will be issued on 2026-10-21. 🎊
The following checklist (instructions) will need to be completed before the issue can be closed. Please copy/paste the Contributor+ Checklist from here into a new comment on this GH and complete it. If you have the K2 extension, you can simply click: [this button]. If no checklist is needed for this issue, you can click: [no checklist button]