Skip to content

[Due for payment 2026-04-23] [Due for payment 2026-04-20] Report - Held expense is automatically unheld after bulk approving report. #87644

Closed

Description

@jponikarchuk

If you haven’t already, check out our contributing guidelines for onboarding and email contributors@expensify.com to request to join our Slack channel!


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.

  1. Open the Expensify app.
  2. Open workspace chat,
  3. Create three manual expenses.
  4. Open the expenses report.
  5. Long tap and select one of the expenses.
  6. Tap on dropdown menu > "Hold"
  7. Enter any reason and save.
  8. Select all the expenses on report.
  9. Tap on dropdown menu and select "Submit"
  10. Select all the expenses again.
  11. Tap on dropdown menu > "Approve"
  12. Note that all the expenses, included the held one, are automatically approved.
  13. Open the held expense.
  14. 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.

Workaround:

Unknown

Platforms:

  • Android: App
  • Android: mWeb Chrome
  • iOS: App
  • iOS: mWeb Safari
  • iOS: mWeb Chrome
  • Windows: Chrome
  • MacOS: Chrome / Safari

Screenshots/Videos

Video.mp4

View all open jobs on GitHub

Issue OwnerCurrent Issue Owner: @Krishna2323

Activity

  1. added
    DeployBlockerCashThis issue or pull request should block deployment
    BugSomething is broken. Auto assigns a BugZero manager.
    on Apr 10, 2026
  2. melvin-bot commented on Apr 10, 2026

    @melvin-bot

    You have been assigned to this deploy blocker because you recently merged this PR: #85796

  3. melvin-bot commented on Apr 10, 2026

    @melvin-bot

    💬 A slack conversation has been started in #expensify-open-source

  4. github-actions commented on Apr 10, 2026

    @github-actions
    Contributor

    👋 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:

    1. Identify the pull request that introduced this issue and revert it.
    2. Find someone who can quickly fix the issue.
    3. Fix the issue yourself.
  5. OSBotify commented on Apr 10, 2026

    @OSBotify
    Contributor

    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 - DeployBlockerCash is correct and stays. This is a frontend (App) bug caused by an App PR.

    Detailed Analysis

    Evidence

    Verification

    Root Cause

    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.

  6. MelvinBot commented on Apr 10, 2026

    @MelvinBot
    Contributor

    Proposal

    What is the root cause of that problem?

    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):

    if (getPlatform() === CONST.PLATFORM.IOS) {
        InteractionManager.runAfterInteractions(() => setIsHoldMenuVisible(true));
    } else {
        setIsHoldMenuVisible(true);
    }
    

    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.

    Investigation details

    Relevant code locations:

    • src/hooks/useSelectionModeReportActions.ts:310-335 — confirmApproval() missing deferral
    • src/hooks/useSelectionModeReportActions.ts:349-357 — confirmPayment() with iOS-only deferral (reference for the fix)
    • src/components/MoneyRequestReportView/MoneyRequestReportActionsList.tsx:1053-1069 — ProcessMoneyReportHoldMenu rendering
    • src/components/ProcessMoneyReportHoldMenu.tsx — DecisionModal wrapper

    Evidence chain:

    1. User selects all expenses and taps Approve from the selection mode dropdown
    2. handleApproveSelected() → confirmApproval() fires synchronously
    3. isAnyTransactionOnHold correctly evaluates to true (via hasHeldExpensesReportUtils at line 153)
    4. setIsHoldMenuVisible(true) fires synchronously (line 315) — no deferral
    5. The ProcessMoneyReportHoldMenu (DecisionModal) attempts to mount while the ButtonWithDropdownMenu popover is still animating closed
    6. On Android, the modal fails to present — the user never sees it
    7. 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.

  7. Krishna2323 commented on Apr 10, 2026

    @Krishna2323
    Contributor

    Working on the PR.

  8. 1 remaining item

  9. roryabraham commented on Apr 10, 2026

    @roryabraham
    Contributor

    It's unclear to me whether this is resolved or not

  10. situchan commented on Apr 11, 2026

    @situchan
    Contributor

    Resolved

    Confirm approval amount modal displayed

    This is expected result

  11. 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
  12. melvin-bot commented on Apr 13, 2026

    @melvin-bot

    Reviewing label has been removed, please complete the "BugZero Checklist".

  13. melvin-bot commented on Apr 13, 2026

    @melvin-bot

    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:

    • @Krishna2323 requires payment through NewDot Manual Requests
    • @situchan requires payment through NewDot Manual Requests
  14. melvin-bot commented on Apr 13, 2026

    @melvin-bot

    @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]

  15. melvin-bot commented on Apr 16, 2026

    @melvin-bot

    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:

    • @Krishna2323 requires payment through NewDot Manual Requests
    • @situchan requires payment through NewDot Manual Requests
  16. 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
  17. melvin-bot commented on Apr 16, 2026

    @melvin-bot

    @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]

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

BugSomething is broken. Auto assigns a BugZero manager.DailyKSv2Engineering

Type

No type

Projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions