You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[Due for payment 2026-09-16] Android - Report - App crashes after keeping one duplicate expense. #97158
Version Number: 9.4.45-0 Reproducible in staging?: Yes Reproducible in production?: N/A - new feature, doesn't exist in prod If this was caught during regression testing, add the test name, ID and link from BrowserStack:#92546 Email or phone of affected tester (no customers):ibellicotest+2766@gmail.com Issue reported by: Applause Internal Team Bug source: Exploratory - Critical User Impact Device used: Motorola MotoG60 / Android 12 (Hybrid app) App Component: Money Requests
Action Performed:
Prerequisite: Account has at least one workspace.
Open the Expensify app.
Open any workspace chat.
Create two similar expenses.
Tap on preview of the second expense created, to navigate directly to that expense.
Tap on "Review Duplicates"
Select the other expense > "Keep Selected" > "Confirm"
Note that app crashes after selection is confirmed.
Expected Result:
User should be able to keep only the selected expense after reviewing duplicates.
Actual Result:
App crashes after keeping on duplicate expense, if report was opened by tapping on preview of one specific expense.
👋 Friendly reminder that deploy blockers are time-sensitive ⏱ issues! Check out the open `StagingDeployCash` deploy checklist to see the list of PRs included in this release, then work quickly to do one of the following:
Identify the pull request that introduced this issue and revert it.
Classification: Frontend bug (fix belongs in Expensify/App) Causing PR: #92546 - "Multi-expense report preview: open the pressed expense (grey View button)" by @wildan-m (High confidence) Related Issues: #91042 (the issue PR #92546 fixed)
Recommendation: REVERT
The crash is caused entirely by the new native navigation path introduced in PR #92546; the duplicate-merge confirmation flow it interacts with was not changed in this release. Reverting #92546 cleanly restores the previous (working) preview-tap behavior without touching the duplicate-review code.
Assigned: @wildan-m (PR author), @MonilBhavsar and @jayeshmangwani (approving reviewers) from PR #92546 Labels: No changes. DeployBlockerCash is kept (an App PR caused this, so the App deploy is correctly blocked). DeployBlocker (Web) is not present and none was added.
📋 Detailed Analysis
Evidence
The attached logs1.txt ends with a hard native crash that terminates the process:
E AndroidRuntime: FATAL EXCEPTION: main
E AndroidRuntime: com.facebook.react.bridge.RetryableMountingLayerException: Unable to find viewState for tag 9542. Surface stopped: false
E AndroidRuntime: at com.facebook.react.fabric.mounting.SurfaceMountingManager.getViewState(SourceFile:1094)
E AndroidRuntime: at com.facebook.react.fabric.mounting.SurfaceMountingManager.updateState(SourceFile:903)
E AndroidRuntime: at com.facebook.react.fabric.mounting.mountitems.IntBufferBatchMountItem.execute(SourceFile:129)
...
I ActivityManager: Process org.me.mobiexpensifyg (pid 28611) has died
This is a Fabric (New Architecture) mounting-layer crash: the renderer tried to apply a updateState mount instruction to a view (tag 9542) that had already been removed from the surface. It is triggered when the navigation state of a still-mounted native stack is mutated out-of-band (a raw reset/dispatch) while React Native is mid-transition — the shadow node's backing view is destroyed underneath an in-flight state update.
Timeline / scope:
Reproducible on staging only; the report explicitly notes this is a new feature that does not exist in production, which is consistent with a staging-only regression from a PR in the current StagingDeployCash (release 9.4.45-0).
Reproduction step 4 — "Tap on preview of the second expense created, to navigate directly to that expense" — is the exact behavior PR #92546 adds. Before #92546, tapping any card in a multi-expense report preview opened the parent report via a single shared handler; #92546 changed each card to open its own specific expense. The crash only reproduces when the report is reached through this new deep-linked preview tap ("if report was opened by tapping on preview of one specific expense"), which is the tell that the new navigation path — not the merge flow itself — is the trigger.
Verification
Files changed by PR #92546 (confirmed via gh pr view 92546 --json files):
Navigation.ts — openExpenseOverParentReport pushes the expense, then on the next microtask manually splices the parent report beneath it with a raw CommonActions.reset dispatch targeted at the live ReportsSplitNavigator:
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- the inserted parent route is keyless; react-navigation mints its key on reset while every existing route (incl. the expense kept on top) retains its key, so nothing underneath remounts
The duplicate-merge confirmation this bug surfaces through was not modified in this release (git log shows no functional change to src/pages/TransactionDuplicate/Confirmation.tsx), so it is not the cause. On native it takes this exit (super-wide RHP is a wide-layout-only concept; WideRHPContextProvider is a no-op on native via index.native.tsx, so isSuperWideRHPDisplayed is always false):
PR #92546 constructs the navigation stack for the tapped expense on native by hand: it pushes the transaction-thread (expense) screen, then splices the parent report underneath the top screen using a direct navigationRef.dispatch(CommonActions.reset(...)) against the ReportsSplitNavigator's internal state (Navigation.ts L1300-L1305). This leaves a manually assembled stack [..., chat, report, expense] whose route objects were re-created outside the normal navigation lifecycle.
When the user then confirms the duplicate merge from that expense's thread, handleMergeDuplicates (Confirmation.tsx L121-L141) tears down the modal and, in the same transition window, replaces the underlying dead thread route via dismissModal({ afterTransition: () => navigate(keptReportRoute, {forceReplace: true}) }). The merge also optimistically deletes/tears down the discarded transaction thread that was spliced into the stack by #92546.
The combination — a stack whose entries were injected via a raw reset, plus a dismissModal + forceReplace that removes/replaces those same entries mid-transition — leaves Fabric applying a queued updateState to a shadow node whose native view has already been destroyed, producing RetryableMountingLayerException: Unable to find viewState for tag ... and a fatal crash. This path only exists on the native narrow layout (the openExpenseOverParentReport branch), which matches the Android-only report and the "opened by tapping the preview of one specific expense" condition. Before #92546, the preview tap opened the parent report through the standard navigation lifecycle and this crash could not occur.
Because the defect is the new manual stack manipulation, a revert of #92546 is the safest resolution. If a roll-forward is preferred, the targeted fix would be to stop hand-splicing the ReportsSplitNavigator state in openExpenseOverParentReport (use a supported navigation primitive to place the report beneath the expense) so the stack tears down through the normal lifecycle during the merge dismissal.
Root cause: The Android crash is a Fabric mount-layer fatal (RetryableMountingLayerException: Unable to find viewState for tag …) caused by PR #92546. On native narrow layouts, tapping an expense preview now routes through openExpenseOverParentReport, which hand-splices the parent report beneath the expense using a raw CommonActions.reset dispatched at the live ReportsSplitNavigator (Navigation.ts:1300-1305). When the user then keeps a duplicate, handleMergeDuplicates tears down the modal and replaces that manually-injected route mid-transition (dismissModal({ afterTransition: () => navigate(keptReportRoute, {forceReplace: true}) })), leaving Fabric applying a queued updateState to an already-destroyed view → crash. This path exists only on the native narrow layout, matching the Android-only, "opened by tapping the preview of one specific expense" reproduction.
Recommended fix — REVERT #92546. This is a deploy blocker for 9.4.45-0; the defect is the new manual stack splicing, and reverting cleanly restores the prior (working) preview-tap behavior without touching the duplicate-review flow. If a roll-forward is preferred over a revert, replace the raw CommonActions.reset in openExpenseOverParentReport with a supported navigation primitive so the spliced report tears down through the normal lifecycle during the merge dismissal.
Proposal
Please re-state the problem that we are trying to solve in this issue.
On Android (Hybrid), the app hard-crashes after keeping one of two duplicate expenses, but only when the expense report was reached by tapping the preview of a specific expense (not via the normal report open).
What is the root cause of that problem?
PR #92546 (in the current StagingDeployCash for 9.4.45-0, merged just before this blocker was filed) added a native-only navigation path for multi-expense report previews. Tapping a card calls navigateToExpense → Navigation.openExpenseOverParentReport, which pushes the expense screen and then, on the next microtask, splices the parent report underneath the top screen via a raw navigationRef.dispatch(CommonActions.reset(...)) targeted at the live ReportsSplitNavigator (Navigation.ts:1300-1305).
Confirming a duplicate merge from that thread runs Confirmation.tsxhandleMergeDuplicates. On native this always takes the final branch (super-wide RHP is wide-layout-only and is a no-op on native; not from Search), which does:
The merge also optimistically tears down the discarded transaction thread that was spliced into the stack. The combination — entries injected via a raw reset + a dismissModal and forceReplace that remove/replace those same entries while a transition is in flight — leaves Fabric applying a queued updateState mount instruction to a shadow node whose native view has already been destroyed:
FATAL EXCEPTION: main
com.facebook.react.bridge.RetryableMountingLayerException: Unable to find viewState for tag 9542. Surface stopped: false
at com.facebook.react.fabric.mounting.SurfaceMountingManager.getViewState
at com.facebook.react.fabric.mounting.SurfaceMountingManager.updateState
The openExpenseOverParentReport reset carries a comment asserting the keyed reset means "nothing underneath remounts" — that assumption holds for the plain preview tap, but breaks once the merge dismissal removes/replaces the spliced routes mid-transition. Before #92546, the preview tap opened the report through the standard lifecycle and this crash could not occur. The duplicate-merge screen itself was not changed this release, so it is not the cause — it merely triggers the fragile stack.
What changes do you think we should make in order to solve the problem?
Revert PR #92546. It is the sole source of the manual native stack splicing that this crash depends on, and reverting restores known-good behavior without any change to the duplicate-review code.
What alternative solutions did you explore?
Roll forward instead of reverting: in openExpenseOverParentReport, stop hand-splicing ReportsSplitNavigator state with CommonActions.reset and instead place the parent report beneath the expense using a supported navigation primitive, so the stack tears down through the normal lifecycle during the merge dismissal. This is riskier for a deploy blocker than a straight revert and should be validated on an Android native narrow-layout build against this exact repro before shipping.
Next Steps for Contributor+ team: To accept:@MelvinBot implement [this](https://github.com/Expensify/App/issues/97158) to create a draft PR. To refine:@MelvinBot <your feedback> To reject: Explain why you are rejecting Melvin's proposal.
changed the title [-]Android - Report - App crashes after keeping one duplicate expense.[/-][+][Due for payment 2026-08-05] Android - Report - App crashes after keeping one duplicate expense.[/+]on Jul 29, 2026
The solution for this issue has been 🚀 deployed to production 🚀 in version 9.4.45-14 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-08-05. 🎊
The following checklist (instructions) will need to be completed before the issue can be closed. Please copy/paste the BugZero 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]
changed the title [-][Due for payment 2026-08-05] Android - Report - App crashes after keeping one duplicate expense.[/-][+][Due for payment 2026-09-16] Android - Report - App crashes after keeping one duplicate expense.[/+]on Sep 9, 2026
The solution for this issue has been 🚀 deployed to production 🚀 in version 9.4.74-2 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-09-16. 🎊
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]
Activity
applause-bot commentedon Jul 27, 2026
While Applause doesn't embed attachments for this bug, here are the links for quick view:
https://platform.applause.com/services/links/v1/external/7d2b6a3b2f4cd83963d786098211caadaa14bc676f4ba585cb156a4a6b44911c
https://platform.applause.com/services/links/v1/external/63a88011f8130f0a52dfc5a871cd8df18357c199c5edd7e3a371769bbdc06d08
melvin-bot commentedon Jul 27, 2026
You have been assigned to this deploy blocker because you recently merged this PR: #92546
melvin-bot commentedon Jul 27, 2026
💬 A slack conversation has been started in #expensify-open-source
github-actions commentedon Jul 27, 2026
👋 Friendly reminder that deploy blockers are time-sensitive ⏱ issues! Check out the open `StagingDeployCash` deploy checklist to see the list of PRs included in this release, then work quickly to do one of the following:
MelvinBot commentedon Jul 27, 2026
🔍 Investigation Summary
Classification: Frontend bug (fix belongs in
Expensify/App)Causing PR: #92546 - "Multi-expense report preview: open the pressed expense (grey View button)" by @wildan-m (High confidence)
Related Issues: #91042 (the issue PR #92546 fixed)
Recommendation: REVERT
The crash is caused entirely by the new native navigation path introduced in PR #92546; the duplicate-merge confirmation flow it interacts with was not changed in this release. Reverting #92546 cleanly restores the previous (working) preview-tap behavior without touching the duplicate-review code.
Assigned: @wildan-m (PR author), @MonilBhavsar and @jayeshmangwani (approving reviewers) from PR #92546
Labels: No changes.
DeployBlockerCashis kept (an App PR caused this, so the App deploy is correctly blocked).DeployBlocker(Web) is not present and none was added.📋 Detailed Analysis
Evidence
The attached
logs1.txtends with a hard native crash that terminates the process:This is a Fabric (New Architecture) mounting-layer crash: the renderer tried to apply a
updateStatemount instruction to a view (tag 9542) that had already been removed from the surface. It is triggered when the navigation state of a still-mounted native stack is mutated out-of-band (a raw reset/dispatch) while React Native is mid-transition — the shadow node's backing view is destroyed underneath an in-flight state update.Timeline / scope:
StagingDeployCash(release9.4.45-0).StagingDeployCashfor9.4.45-0and merged 2026-07-27, immediately before this blocker was filed.Reproduction step 4 — "Tap on preview of the second expense created, to navigate directly to that expense" — is the exact behavior PR #92546 adds. Before #92546, tapping any card in a multi-expense report preview opened the parent report via a single shared handler; #92546 changed each card to open its own specific expense. The crash only reproduces when the report is reached through this new deep-linked preview tap ("if report was opened by tapping on preview of one specific expense"), which is the tell that the new navigation path — not the merge flow itself — is the trigger.
Verification
Files changed by PR #92546 (confirmed via
gh pr view 92546 --json files):src/components/ReportActionItem/MoneyRequestReportPreview/index.tsxsrc/libs/Navigation/Navigation.tssrc/components/ReportActionItem/MoneyRequestReportPreview/ReportPreviewActionButton.tsxtests/ui/*,cspell.jsonOn a narrow (Android) layout, tapping an expense card routes into the new native-only helper:
MoneyRequestReportPreview/index.tsx—navigateToExpensesends narrow-layout native taps toNavigation.openExpenseOverParentReport(...):App/src/components/ReportActionItem/MoneyRequestReportPreview/index.tsx
Lines 216 to 245 in 9a3e4df
Navigation.ts—openExpenseOverParentReportpushes the expense, then on the next microtask manually splices the parent report beneath it with a rawCommonActions.resetdispatch targeted at the liveReportsSplitNavigator:App/src/libs/Navigation/Navigation.ts
Lines 1274 to 1307 in 9a3e4df
The duplicate-merge confirmation this bug surfaces through was not modified in this release (
git logshows no functional change tosrc/pages/TransactionDuplicate/Confirmation.tsx), so it is not the cause. On native it takes this exit (super-wide RHP is a wide-layout-only concept;WideRHPContextProvideris a no-op on native viaindex.native.tsx, soisSuperWideRHPDisplayedis alwaysfalse):App/src/pages/TransactionDuplicate/Confirmation.tsx
Lines 121 to 141 in 9a3e4df
Root Cause
PR #92546 constructs the navigation stack for the tapped expense on native by hand: it pushes the transaction-thread (expense) screen, then splices the parent report underneath the top screen using a direct
navigationRef.dispatch(CommonActions.reset(...))against theReportsSplitNavigator's internal state (Navigation.tsL1300-L1305). This leaves a manually assembled stack[..., chat, report, expense]whose route objects were re-created outside the normal navigation lifecycle.When the user then confirms the duplicate merge from that expense's thread,
handleMergeDuplicates(Confirmation.tsxL121-L141) tears down the modal and, in the same transition window, replaces the underlying dead thread route viadismissModal({ afterTransition: () => navigate(keptReportRoute, {forceReplace: true}) }). The merge also optimistically deletes/tears down the discarded transaction thread that was spliced into the stack by #92546.The combination — a stack whose entries were injected via a raw reset, plus a
dismissModal+forceReplacethat removes/replaces those same entries mid-transition — leaves Fabric applying a queuedupdateStateto a shadow node whose native view has already been destroyed, producingRetryableMountingLayerException: Unable to find viewState for tag ...and a fatal crash. This path only exists on the native narrow layout (theopenExpenseOverParentReportbranch), which matches the Android-only report and the "opened by tapping the preview of one specific expense" condition. Before #92546, the preview tap opened the parent report through the standard navigation lifecycle and this crash could not occur.Because the defect is the new manual stack manipulation, a revert of #92546 is the safest resolution. If a roll-forward is preferred, the targeted fix would be to stop hand-splicing the
ReportsSplitNavigatorstate inopenExpenseOverParentReport(use a supported navigation primitive to place the report beneath the expense) so the stack tears down through the normal lifecycle during the merge dismissal.MelvinBot commentedon Jul 27, 2026
Issue Analysis
Root cause: The Android crash is a Fabric mount-layer fatal (
RetryableMountingLayerException: Unable to find viewState for tag …) caused by PR #92546. On native narrow layouts, tapping an expense preview now routes throughopenExpenseOverParentReport, which hand-splices the parent report beneath the expense using a rawCommonActions.resetdispatched at the liveReportsSplitNavigator(Navigation.ts:1300-1305). When the user then keeps a duplicate,handleMergeDuplicatestears down the modal and replaces that manually-injected route mid-transition (dismissModal({ afterTransition: () => navigate(keptReportRoute, {forceReplace: true}) })), leaving Fabric applying a queuedupdateStateto an already-destroyed view → crash. This path exists only on the native narrow layout, matching the Android-only, "opened by tapping the preview of one specific expense" reproduction.Recommended fix — REVERT #92546. This is a deploy blocker for
9.4.45-0; the defect is the new manual stack splicing, and reverting cleanly restores the prior (working) preview-tap behavior without touching the duplicate-review flow. If a roll-forward is preferred over a revert, replace the rawCommonActions.resetinopenExpenseOverParentReportwith a supported navigation primitive so the spliced report tears down through the normal lifecycle during the merge dismissal.Proposal
Please re-state the problem that we are trying to solve in this issue.
On Android (Hybrid), the app hard-crashes after keeping one of two duplicate expenses, but only when the expense report was reached by tapping the preview of a specific expense (not via the normal report open).
What is the root cause of that problem?
PR #92546 (in the current
StagingDeployCashfor9.4.45-0, merged just before this blocker was filed) added a native-only navigation path for multi-expense report previews. Tapping a card callsnavigateToExpense→Navigation.openExpenseOverParentReport, which pushes the expense screen and then, on the next microtask, splices the parent report underneath the top screen via a rawnavigationRef.dispatch(CommonActions.reset(...))targeted at the liveReportsSplitNavigator(Navigation.ts:1300-1305).Confirming a duplicate merge from that thread runs
Confirmation.tsxhandleMergeDuplicates. On native this always takes the final branch (super-wide RHP is wide-layout-only and is a no-op on native; not from Search), which does:The merge also optimistically tears down the discarded transaction thread that was spliced into the stack. The combination — entries injected via a raw reset + a
dismissModalandforceReplacethat remove/replace those same entries while a transition is in flight — leaves Fabric applying a queuedupdateStatemount instruction to a shadow node whose native view has already been destroyed:The
openExpenseOverParentReportreset carries a comment asserting the keyed reset means "nothing underneath remounts" — that assumption holds for the plain preview tap, but breaks once the merge dismissal removes/replaces the spliced routes mid-transition. Before #92546, the preview tap opened the report through the standard lifecycle and this crash could not occur. The duplicate-merge screen itself was not changed this release, so it is not the cause — it merely triggers the fragile stack.What changes do you think we should make in order to solve the problem?
Revert PR #92546. It is the sole source of the manual native stack splicing that this crash depends on, and reverting restores known-good behavior without any change to the duplicate-review code.
What alternative solutions did you explore?
Roll forward instead of reverting: in
openExpenseOverParentReport, stop hand-splicingReportsSplitNavigatorstate withCommonActions.resetand instead place the parent report beneath the expense using a supported navigation primitive, so the stack tears down through the normal lifecycle during the merge dismissal. This is riskier for a deploy blocker than a straight revert and should be validated on an Android native narrow-layout build against this exact repro before shipping.Next Steps for Contributor+ team:
To accept:
@MelvinBot implement [this](https://github.com/Expensify/App/issues/97158)to create a draft PR.To refine:
@MelvinBot <your feedback>To reject: Explain why you are rejecting Melvin's proposal.
IuliiaHerets commentedon Jul 28, 2026
Not repro on Samsung Galaxy A40/Android 11/App/Build v9.4.45-1
mobizen_20260728_114622.mp4
melvin-bot commentedon Jul 28, 2026
If you are the assigned CME please investigate whether the linked PR caused a regression and leave a comment with the results.
If a regression has occurred and you are the assigned CM follow the instructions here.
If this regression could have been avoided please consider also proposing a recommendation to the PR checklist so that we can avoid it in the future.
melvin-bot commentedon Jul 28, 2026
If you are the assigned CME please investigate whether the linked PR caused a regression and leave a comment with the results.
If a regression has occurred and you are the assigned CM follow the instructions here.
If this regression could have been avoided please consider also proposing a recommendation to the PR checklist so that we can avoid it in the future.
[-]Android - Report - App crashes after keeping one duplicate expense.[/-][+][Due for payment 2026-08-05] Android - Report - App crashes after keeping one duplicate expense.[/+]melvin-bot commentedon Jul 29, 2026
@MonilBhavsar @jayeshmangwani
The solution for this issue has been 🚀 deployed to production 🚀 in version 9.4.45-14 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-08-05. 🎊
The following checklist (instructions) will need to be completed before the issue can be closed. Please copy/paste the BugZero 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]
Open the narrow-layout expense in the RHP and keep deleted rows out o…
Open the pressed expense from a multi-expense report preview
jponikarchuk commentedon Sep 9, 2026
Deploy Blocker #100673 was identified to be related to the PR #97308 that attempted to fix this issue.
[-][Due for payment 2026-08-05] Android - Report - App crashes after keeping one duplicate expense.[/-][+][Due for payment 2026-09-16] Android - Report - App crashes after keeping one duplicate expense.[/+]melvin-bot commentedon Sep 9, 2026
@jayeshmangwani
The solution for this issue has been 🚀 deployed to production 🚀 in version 9.4.74-2 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-09-16. 🎊
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]