Skip to content

[Due for payment 2026-09-23] Spend- Unable to submit report modal does not open when one of the reports cannot be submitted #99028

Closed

Description

@applause-bot

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.57-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: #96922
Email or phone of affected tester (no customers): sdsiodjnosdjiodsj@gmail.com
Issue reported by: Applause Internal Team
Bug source: Exploratory - Significant User Experience Deterioration
Device used: Mac 26.5 / Chrome
App Component: Money Requests

Action Performed:

  1. Go to staging.new.expensify.com
  2. Go to workspace chat.
  3. Create an expense and hold it.
  4. Go to Spend > Reports.
  5. Select the expense via checkbox.
  6. Click dropdown button > Submit.
    → Unable to submit report modal opens.
  7. Create a new report and add an expense. Do not hold this expense.
  8. Go to Spend > Reports.
  9. Select both expenses via checkbox.
  10. Click dropdown button > Submit.

Expected Result:

Unable to submit report modal will open because one of the reports cannot be submitted.

Actual Result:

Unable to submit report modal does not open when one of the reports cannot be submitted.

Workaround:

Unknown

Platforms:

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

Screenshots/Videos

Bug7237904_1787201881864.2.mp4

View all open jobs on GitHub

Applause Internal Information
Exported by: Mita Rachim
Bug ID: 7237904

Issue OwnerCurrent Issue Owner: @mallenexpensify

Activity

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

    @applause-bot
    Author

    While Applause doesn't embed attachments for this bug, here are the links for quick view:
    https://platform.applause.com/services/links/v1/external/80962538eb4492ad9ee6c979cdafccff6baccea406d1b8b77342a7dd1de7993e

  3. melvin-bot commented on Aug 20, 2026

    @melvin-bot

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

  4. melvin-bot commented on Aug 20, 2026

    @melvin-bot

    @NikkiWines @huult you have been assigned to this deploy blocker because you reviewed the offending PR: #96922

  5. github-actions commented on Aug 20, 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.
  6. melvin-bot commented on Aug 20, 2026

    @melvin-bot

    @c3024 you have been assigned to this deploy blocker because you authored the offending PR: #96922

  7. melvin-bot commented on Aug 20, 2026

    @melvin-bot

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

  8. MelvinBot commented on Aug 20, 2026

    @MelvinBot
    Contributor

    🔍 Investigation Summary

    Classification: Frontend bug
    Causing PR: #96922 - "Split held expenses onto a new Draft report when submitting offline" by @c3024 (High confidence)
    Related Issues: None

    Recommendation: ROLL FORWARD

    The bulk (multi-select) Submit guard checks hasOnlyHeldExpenses against a single flattened list of transactions spanning all selected reports, so it only fires when every selected transaction is held. The fix is a small, well-scoped change (evaluate the guard per report), which is far less risky than reverting this 28-file feature PR.

    Assigned: @c3024 (PR author), @NikkiWines and @huult (approving reviewers) from PR #96922 — already assigned
    Labels: No change. DeployBlockerCash is kept (App deploy is blocked). DeployBlocker (Web) is not present, so nothing to remove.

    📋 Detailed Analysis

    Evidence

    • PR Split held expenses onto a new Draft report when submitting offline #96922 (merged 2026-08-19) added the held-expense block guard to the bulk Submit flow in src/hooks/useSearchBulkActions.ts. The issue was reported on 2026-08-20 against v9.4.57-0, which is the deploy that includes this PR.
    • The feature is new and does not exist in production, matching "Reproducible in production?: N/A - new feature".
    • The single-report Submit case works (correctly opens the modal); only the multi-report case fails — exactly the signature of a per-report predicate being applied to a cross-report aggregate.
    • The same PR applies hasOnlyHeldExpenses per report in every single-report path (submitReport in SubmitPrimaryAction.tsx, handleActionButtonPress in SubmitActionButton.tsx), confirming the intended per-report semantics that the bulk path violates.

    Verification

    Root Cause

    In the bulk Submit handler, allSelectedTransactionsList is built by flattening the transactions of all selected reports into one array:

    const allSelectedTransactionsList = selectedReports.length
        ? Object.values(allTransactions ?? {}).filter((t) => selectedReports.some((report) => report.reportID === t.reportID))
        : selectedTransactionsKeys.map(...).filter(...);
    
    if (hasOnlyHeldExpenses(allSelectedTransactionsList)) {   // L2267
        showHeldExpensesBlockModal(showConfirmModal, translate);
        return;
    }

    hasOnlyHeldExpenses is a per-report predicate ("this report contains only held expenses, so it cannot be submitted"). When two reports are selected — one all-held, one submittable — the flattened list contains a non-held transaction, so hasOnlyHeldExpenses returns false, the "Unable to submit report" modal never appears, and the flow proceeds to submit (silently skipping the un-submittable held report).

    The fix should group the selected transactions by report and show the block modal when any selected report consists only of held expenses (i.e. selectedReports.some(report => hasOnlyHeldExpenses(transactionsForReport))), mirroring the per-report checks already used in the single-report Submit paths. Note the hasOnlyPendingCardTransactions(allSelectedTransactionsList) guard immediately above (L2262) has the identical cross-report flattening flaw and should be fixed the same way.

    💡 Suggested AI code review rule

    This bug looks like a recurring, mechanically detectable pattern, so it may be worth a coding-standards rule so the AI reviewer catches the next one. Proposed for Expensify/App:

    • Rule: CONSISTENCY-NN Evaluate per-report guard predicates per report in bulk/multi-select flows
    • Condition: In a bulk/multi-select handler, a report-scoped "all/only" predicate (hasOnly*, e.g. hasOnlyHeldExpenses, hasOnlyPendingCardTransactions) is called on a transaction list aggregated across multiple reports. Group by reportID and evaluate the predicate per report (.some(report => hasOnly...(txnsForReport))) instead.
    • Why: A per-report "only-X" predicate applied to a cross-report flattened list returns false as soon as any other report contributes a non-X transaction, silently defeating the guard for the reports that should be blocked (this issue: the "Unable to submit" modal never opens in multi-select).
    • Search pattern: hasOnly[A-Za-z]+\((?:all|.*[Ss]elected).*[Tt]ransaction in bulk-action hooks/handlers (e.g. src/hooks/useSearchBulkActions.ts).
    • Offending code (❌):
      if (hasOnlyPendingCardTransactions(allSelectedTransactionsList)) {
      showPendingCardTransactionsBlockModal(showConfirmModal, translate);
      return;
      }
      if (hasOnlyHeldExpenses(allSelectedTransactionsList)) {
      showHeldExpensesBlockModal(showConfirmModal, translate);
      return;
      }
    • Fix (✅): group transactions by report and use selectedReports.some(report => hasOnlyHeldExpenses(transactionsForReport)), mirroring the single-report paths in this PR.

    The full rule file, with the ❌/✅ code inline in Expensify/App's coding-standards format, would go in that repo. If it's worth adding, a maintainer can open it there as a draft PR.


    view run

  9. 38 remaining items

  10. huult commented on Sep 17, 2026

    @huult
    Contributor

    Issue #101278 is not a regression, per this comment.

  11. added
    Awaiting PaymentAuto-added when associated PR is deployed to production
    and removed on Sep 22, 2026
  12. melvin-bot commented on Sep 22, 2026

    @melvin-bot

    Triggered auto assignment to @mallenexpensify (Awaiting Payment)

  13. melvin-bot commented on Sep 22, 2026

    @melvin-bot

    Payment Summary

    Resolving PRs:

    BugZero Checklist (@mallenexpensify)

    • I have confirmed assignees, roles, and Upwork contracts look correct
    • I have paid out Upwork contracts / manual NewDot requests
    • [BugZero Assignee] I have created a GH issue for creating/updating the regression test once above steps have been agreed upon
  14. mallenexpensify commented on Sep 24, 2026

    @mallenexpensify
    Contributor

    Payment Summary

    Contributor+: @huult due $250 via NewDot

    Bit of a weird one but that above looks correct. thx

  15. JmillsExpensify commented on Oct 14, 2026

    @JmillsExpensify
    Contributor

    $250 approved for @huult

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

Metadata

Metadata

Labels

Awaiting PaymentAuto-added when associated PR is deployed to productionBugSomething is broken. Auto assigns a BugZero manager.EngineeringReviewingHas a PR in reviewWeeklyKSv2

Type

No type

Projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions