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-04-23] [Due for payment 2026-04-20] Report - Held expense is automatically unheld after bulk approving report. #87644
Version Number: 9.3.58-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:#85796 Email or phone of affected tester (no customers):ibellicotest+2250@gmail.com Issue reported by: Applause Internal Team Bug source: Pull Request QA execution Device used: Motorola MotoG60 / Android 12 (Hybrid app) - Windows 11 / Chrome App Component: Money Requests
Action Performed:
Prerequisite: Account has at least one workspace.
Prerequisite 2: Delayed submissions, approvals and payments enabled.
Open the Expensify app.
Open workspace chat,
Create three manual expenses.
Open the expenses report.
Long tap and select one of the expenses.
Tap on dropdown menu > "Hold"
Enter any reason and save.
Select all the expenses on report.
Tap on dropdown menu and select "Submit"
Select all the expenses again.
Tap on dropdown menu > "Approve"
Note that all the expenses, included the held one, are automatically approved.
Open the held expense.
Note that "Unheld this expenses" message is displayed.
Expected Result:
Hold modal should appear when trying to approve report with held expenses. Held expenses shouldn´t be automatically approved.
Actual Result:
Held expenses are automatically approved and unheld after bulk approving report. "Hold" modal is not displayed.
👋 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 Causing PR: #85796 - "Add selection mode Submit/Approve/Pay actions for mobile layout." by @Krishna2323 (High confidence) Related Issues: #72502 (the issue this PR fixed)
Recommendation: REVERT
PR #85796 introduced the new selection-mode Submit/Approve/Pay actions for the mobile layout. The hold modal is not being shown when approving a report with held expenses via this new selection mode path, causing held expenses to be automatically approved and unheld. The PR is the direct cause and should be reverted until the hold modal integration in the selection mode flow is fixed.
Assigned: @Krishna2323 (PR author), @situchan (approving reviewer), @puneetlath (approving reviewer) Labels: No label changes - DeployBlockerCash is correct and stays. This is a frontend (App) bug caused by an App PR.
The bug is staging-only ("Reproducible in production?: N/A - new feature, does not exist in prod"), confirming it was introduced by this staging PR
The PR introduced the useSelectionModeReportActions hook and wired it into MoneyRequestReportActionsList.tsx for mobile selection mode approve/submit/pay
Verification
Affected files: src/hooks/useSelectionModeReportActions.ts (new file, added by this PR) and src/components/MoneyRequestReportView/MoneyRequestReportActionsList.tsx (modified by this PR)
In src/hooks/useSelectionModeReportActions.ts, the confirmApproval() function checks isAnyTransactionOnHold before calling setIsHoldMenuVisible(true). The ProcessMoneyReportHoldMenu is rendered in MoneyRequestReportActionsList.tsx and is gated on isHoldMenuVisible && requestType !== undefined. Despite the logic appearing structurally correct, the hold modal is not being shown in practice on Android HybridApp when approving through the selection mode dropdown. The handleHoldMenuConfirm callback only clears selections and exits selection mode - the actual approveMoneyRequest call is supposed to be executed inside useHoldMenuSubmit (called via ProcessMoneyReportHoldMenu) when the user confirms in the modal. Since the modal never appears, the approval path falls through directly to approveMoneyRequest without the hold gate, unheld the held expense automatically.
The selection-mode approve path in useSelectionModeReportActions.ts calls setIsHoldMenuVisible(true) synchronously inside confirmApproval() (line 315). This state update fires while the ButtonWithDropdownMenu popover is still dismissing its animation, causing the ProcessMoneyReportHoldMenu modal (a DecisionModal) to fail to appear on Android.
The payment path already has a fix for this exact issue on iOS — it defers the state update using InteractionManager.runAfterInteractions (lines 351-354):
The approve path is missing this deferral entirely, and the popover/modal conflict also affects Android (the existing iOS-only fix in the payment path may be insufficient for Android as well).
Because the hold menu never visually appears, the user's tap on "Approve" falls through to approveMoneyRequest with full: true, which approves and unholds all expenses.
What changes do you think we should make in order to solve the problem?
Apply the same InteractionManager.runAfterInteractions deferral to confirmApproval() in useSelectionModeReportActions.ts (line 315). The deferral should apply on both iOS and Android (not just iOS as the payment path currently does), since the popover dismiss animation conflicts with modal presentation on both platforms.
Additionally, the existing payment path's iOS-only guard should be extended to also cover Android, for the same reason.
What alternative solutions did you explore?
An alternative is to add a small setTimeout delay before showing the hold menu, but InteractionManager.runAfterInteractions is the idiomatic React Native approach and is already used in the adjacent payment path.
Another alternative is to restructure the dropdown to wait for its popover close animation to complete before firing onSelected, but that would be a larger change with wider impact.
isAnyTransactionOnHold correctly evaluates to true (via hasHeldExpensesReportUtils at line 153)
setIsHoldMenuVisible(true) fires synchronously (line 315) — no deferral
The ProcessMoneyReportHoldMenu (DecisionModal) attempts to mount while the ButtonWithDropdownMenu popover is still animating closed
On Android, the modal fails to present — the user never sees it
Since the hold menu was "shown" (state-wise) but never confirmed, no approval action fires from the hold menu. However, if the modal fails to mount entirely and the state cleanup doesn't happen, it's possible the approval proceeds directly.
Alternative explanation considered: Stale transaction data causing isAnyTransactionOnHold to return false. This was ruled out — hasHeldExpensesReportUtils reads from getReportTransactions which uses deprecatedReportsTransactions (Onyx data), and the held status (transaction.comment.hold) persists through submit. The hold check logic is identical to the working non-selection-mode path in useConfirmApproval.ts.
Possibly related issue:#78130 — Hold educational modal displayed incorrectly on Reports in selection mode.
Next Steps for Contributor+ team: Reply with @MelvinBot implement this to create a draft PR, @MelvinBot <your feedback> to refine this analysis, or explain why you are rejecting Melvin's proposal.
changed the title [-]Report - Held expense is automatically unheld after bulk approving report.[/-][+][Due for payment 2026-04-20] Report - Held expense is automatically unheld after bulk approving report.[/+]on Apr 13, 2026
The solution for this issue has been 🚀 deployed to production 🚀 in version 9.3.58-9 and is now subject to a 7-day regression period 📆. Here is the list of pull requests that resolve this issue:
@Krishna2323 / @situchan The PR fixing this issue has been merged! 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]
The solution for this issue has been 🚀 deployed to production 🚀 in version 9.3.59-4 and is now subject to a 7-day regression period 📆. Here is the list of pull requests that resolve this issue:
changed the title [-][Due for payment 2026-04-20] Report - Held expense is automatically unheld after bulk approving report.[/-][+][Due for payment 2026-04-23] [Due for payment 2026-04-20] Report - Held expense is automatically unheld after bulk approving report.[/+]on Apr 16, 2026
@Krishna2323 / @situchan The PR fixing this issue has been merged! 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]
Activity
melvin-bot commentedon Apr 10, 2026
You have been assigned to this deploy blocker because you recently merged this PR: #85796
melvin-bot commentedon Apr 10, 2026
💬 A slack conversation has been started in #expensify-open-source
github-actions commentedon Apr 10, 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:
OSBotify commentedon Apr 10, 2026
Investigation Summary
Classification: Frontend bug
Causing PR: #85796 - "Add selection mode Submit/Approve/Pay actions for mobile layout." by @Krishna2323 (High confidence)
Related Issues: #72502 (the issue this PR fixed)
Recommendation: REVERT
PR #85796 introduced the new selection-mode Submit/Approve/Pay actions for the mobile layout. The hold modal is not being shown when approving a report with held expenses via this new selection mode path, causing held expenses to be automatically approved and unheld. The PR is the direct cause and should be reverted until the hold modal integration in the selection mode flow is fixed.
Assigned: @Krishna2323 (PR author), @situchan (approving reviewer), @puneetlath (approving reviewer)
Labels: No label changes -
DeployBlockerCashis correct and stays. This is a frontend (App) bug caused by an App PR.Detailed Analysis
Evidence
useSelectionModeReportActionshook and wired it intoMoneyRequestReportActionsList.tsxfor mobile selection mode approve/submit/payVerification
src/hooks/useSelectionModeReportActions.ts(new file, added by this PR) andsrc/components/MoneyRequestReportView/MoneyRequestReportActionsList.tsx(modified by this PR)Root Cause
In
src/hooks/useSelectionModeReportActions.ts, theconfirmApproval()function checksisAnyTransactionOnHoldbefore callingsetIsHoldMenuVisible(true). TheProcessMoneyReportHoldMenuis rendered inMoneyRequestReportActionsList.tsxand is gated onisHoldMenuVisible && requestType !== undefined. Despite the logic appearing structurally correct, the hold modal is not being shown in practice on Android HybridApp when approving through the selection mode dropdown. ThehandleHoldMenuConfirmcallback only clears selections and exits selection mode - the actualapproveMoneyRequestcall is supposed to be executed insideuseHoldMenuSubmit(called viaProcessMoneyReportHoldMenu) when the user confirms in the modal. Since the modal never appears, the approval path falls through directly toapproveMoneyRequestwithout the hold gate, unheld the held expense automatically.MelvinBot commentedon Apr 10, 2026
Proposal
What is the root cause of that problem?
The selection-mode approve path in
useSelectionModeReportActions.tscallssetIsHoldMenuVisible(true)synchronously insideconfirmApproval()(line 315). This state update fires while theButtonWithDropdownMenupopover is still dismissing its animation, causing theProcessMoneyReportHoldMenumodal (aDecisionModal) to fail to appear on Android.The payment path already has a fix for this exact issue on iOS — it defers the state update using
InteractionManager.runAfterInteractions(lines 351-354):The approve path is missing this deferral entirely, and the popover/modal conflict also affects Android (the existing iOS-only fix in the payment path may be insufficient for Android as well).
Because the hold menu never visually appears, the user's tap on "Approve" falls through to
approveMoneyRequestwithfull: true, which approves and unholds all expenses.What changes do you think we should make in order to solve the problem?
Apply the same
InteractionManager.runAfterInteractionsdeferral toconfirmApproval()inuseSelectionModeReportActions.ts(line 315). The deferral should apply on both iOS and Android (not just iOS as the payment path currently does), since the popover dismiss animation conflicts with modal presentation on both platforms.Additionally, the existing payment path's iOS-only guard should be extended to also cover Android, for the same reason.
What alternative solutions did you explore?
An alternative is to add a small
setTimeoutdelay before showing the hold menu, butInteractionManager.runAfterInteractionsis the idiomatic React Native approach and is already used in the adjacent payment path.Another alternative is to restructure the dropdown to wait for its popover close animation to complete before firing
onSelected, but that would be a larger change with wider impact.Investigation details
Relevant code locations:
src/hooks/useSelectionModeReportActions.ts:310-335—confirmApproval()missing deferralsrc/hooks/useSelectionModeReportActions.ts:349-357—confirmPayment()with iOS-only deferral (reference for the fix)src/components/MoneyRequestReportView/MoneyRequestReportActionsList.tsx:1053-1069—ProcessMoneyReportHoldMenurenderingsrc/components/ProcessMoneyReportHoldMenu.tsx—DecisionModalwrapperEvidence chain:
handleApproveSelected()→confirmApproval()fires synchronouslyisAnyTransactionOnHoldcorrectly evaluates totrue(viahasHeldExpensesReportUtilsat line 153)setIsHoldMenuVisible(true)fires synchronously (line 315) — no deferralProcessMoneyReportHoldMenu(DecisionModal) attempts to mount while theButtonWithDropdownMenupopover is still animating closedAlternative explanation considered: Stale transaction data causing
isAnyTransactionOnHoldto returnfalse. This was ruled out —hasHeldExpensesReportUtilsreads fromgetReportTransactionswhich usesdeprecatedReportsTransactions(Onyx data), and the held status (transaction.comment.hold) persists through submit. The hold check logic is identical to the working non-selection-mode path inuseConfirmApproval.ts.Possibly related issue: #78130 — Hold educational modal displayed incorrectly on Reports in selection mode.
Next Steps for Contributor+ team: Reply with
@MelvinBot implement thisto create a draft PR,@MelvinBot <your feedback>to refine this analysis, or explain why you are rejecting Melvin's proposal.Krishna2323 commentedon Apr 10, 2026
Working on the PR.
1 remaining item
roryabraham commentedon Apr 10, 2026
It's unclear to me whether this is resolved or not
situchan commentedon Apr 11, 2026
Resolved
This is expected result
[-]Report - Held expense is automatically unheld after bulk approving report.[/-][+][Due for payment 2026-04-20] Report - Held expense is automatically unheld after bulk approving report.[/+]melvin-bot commentedon Apr 13, 2026
Reviewinglabel has been removed, please complete the "BugZero Checklist".melvin-bot commentedon Apr 13, 2026
The solution for this issue has been 🚀 deployed to production 🚀 in version 9.3.58-9 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-04-20. 🎊
For reference, here are some details about the assignees on this issue:
melvin-bot commentedon Apr 13, 2026
@Krishna2323 / @situchan The PR fixing this issue has been merged! 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]
melvin-bot commentedon Apr 16, 2026
The solution for this issue has been 🚀 deployed to production 🚀 in version 9.3.59-4 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-04-23. 🎊
For reference, here are some details about the assignees on this issue:
[-][Due for payment 2026-04-20] Report - Held expense is automatically unheld after bulk approving report.[/-][+][Due for payment 2026-04-23] [Due for payment 2026-04-20] Report - Held expense is automatically unheld after bulk approving report.[/+]melvin-bot commentedon Apr 16, 2026
@Krishna2323 / @situchan The PR fixing this issue has been merged! 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]