Skip to content

[Due for payment 2026-04-30] Duplicate expense - Page become unresponsive when duplicating bulk expenses #87604

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

Action Performed:

  1. Navigate to staging.new.expensify.com
  2. Sign in with 200+ expense account ( you can duplicate until you have that amount)
  3. Go to reports and select all expense and duplicate

Expected Result:

The expense will be duplicated without an issue

Actual Result:

The page become unresponsive when duplicating a large amount of expense

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: #84657

  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: #84657 - "[Duplicate Expense] Allow bulk duplication." by @Krishna2323 (High confidence)
    Related Issues: #84281

    Recommendation: REVERT

    PR #84657 introduced a synchronous loop that calls duplicateExpenseTransaction once per selected transaction with no batching or throttling. With 200+ expenses selected, this triggers hundreds of back-to-back Onyx writes and API preparations on the main JS thread, blocking the event loop and making the page unresponsive.

    Assigned: @Krishna2323 (PR author), @JS00001 @situchan (approving reviewers)
    Labels: No label changes needed — DeployBlockerCash is correct (frontend App PR caused the issue; this is staging-only and does not exist in production)

    Detailed Analysis

    Evidence

    Verification

    • Affected code: src/libs/actions/IOU/Duplicate.ts — specifically the bulkDuplicateExpenses function
    • Confirmation: PR [Duplicate Expense] Allow bulk duplication.  #84657 adds src/libs/actions/IOU/Duplicate.ts, src/hooks/useBulkDuplicateAction.ts, and src/components/Search/BulkDuplicateHandler.tsx — all directly on the path of the reported bug

    Root Cause

    bulkDuplicateExpenses in Duplicate.ts (lines 960-1034) runs a synchronous for loop over all selected transactions, calling duplicateExpenseTransaction for each one. Each call performs complex work including building optimistic Onyx updates, generating new report/transaction IDs, and preparing API parameters. With 200+ transactions this executes entirely on the main JS thread without yielding, causing a browser hang. There is no batching, chunking, or async deferral between iterations.

  6. MelvinBot commented on Apr 10, 2026

    @MelvinBot
    Contributor

    Proposal

    What is the root cause of that problem?

    bulkDuplicateExpenses() in src/libs/actions/IOU/Duplicate.ts:995-1031 runs a synchronous for-loop over all selected transactions. Each iteration calls duplicateExpenseTransaction() → requestMoney() → API.write(), which synchronously applies ~10-15 Onyx optimistic updates via Onyx.update(). For 200+ expenses, this means 2000-3000+ synchronous Onyx state updates in a single uninterrupted JS frame, completely blocking the UI thread.

    There is no batching, chunking, setTimeout yielding, or InteractionManager usage anywhere in this path.

    What changes do you think we should make in order to solve the problem?

    Break the synchronous loop in bulkDuplicateExpenses() (Duplicate.ts:995) into async chunks that yield to the event loop between batches. For example, process 5-10 transactions per chunk using setTimeout(..., 0) or requestAnimationFrame between chunks so the browser can paint and handle user input between batches.

    A simple approach:

    1. Convert the for-loop into an async function that processes a small batch (e.g., 5-10 items), then yields via await new Promise(resolve => setTimeout(resolve, 0)) before the next batch.
    2. Optionally show a progress indicator during the operation so the user knows duplication is in progress.

    What alternative solutions did you explore? (Optional)

    • Single batched API call: Instead of calling API.write() per transaction, send all duplications in one API command. This would be more efficient but requires backend changes and a new API endpoint — higher effort for a deploy blocker fix.
    • Web Worker: Offload Onyx updates to a worker thread. Not feasible since Onyx updates must happen on the main thread to trigger React re-renders.
    Investigation details

    Data flow causing the freeze:

    1. User clicks "Duplicate" with 200+ selected transactions
    2. useBulkDuplicateAction.handleDuplicate() calls bulkDuplicateExpenses()
    3. bulkDuplicateExpenses() enters a synchronous for-loop at Duplicate.ts:995
    4. Each iteration: duplicateExpenseTransaction() → createExpenseByType() → requestMoney()
    5. requestMoney() calls API.write() → prepareRequest() → Onyx.update(optimisticData) synchronously
    6. buildOnyxDataForMoneyRequest (src/libs/actions/IOU/index.ts:1668-2386) generates ~36 Onyx push operations per transaction (~10-15 optimistic ones applied synchronously)
    7. Steps 4-6 repeat 200+ times without ever yielding to the event loop
    8. JS thread is blocked for the entire duration → page unresponsive

    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. situchan commented on Apr 10, 2026

    @situchan
    Contributor

    200+ expense 😮

    This feature is gated behind production so we can demote.

  8. JS00001 commented on Apr 10, 2026

    @JS00001
    Contributor

    Demoti9ng since this is locked from prod

  9. 15 remaining items

  10. jamesdeanexpensify commented on Apr 13, 2026

    @jamesdeanexpensify
    Contributor

    Love that copy - maybe just update the header to Duplicate expenses to match the button?

  11. trjExpensify commented on Apr 13, 2026

    @trjExpensify
    Contributor

    Love that copy - maybe just update the header to Duplicate expenses to match the button?

    Which one to confirm? My written edit or the one in the screenshot?

  12. jamesdeanexpensify commented on Apr 13, 2026

    @jamesdeanexpensify
    Contributor

    Ah, I actually don't mind the one in the screenshot.

  13. JS00001 commented on Apr 14, 2026

    @JS00001
    Contributor

    @Krishna2323 lets use the current one then

  14. JS00001 commented on Apr 17, 2026

    @JS00001
    Contributor

    PR to fix this blocker is merged: #87697

  15. melvin-bot commented on Apr 21, 2026

    @melvin-bot

    ⚠️ Looks like this issue was linked to a Deploy Blocker here

    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.

  16. changed the title [-]Duplicate expense - Page become unresponsive when duplicating bulk expenses[/-] [+][Due for payment 2026-04-30] Duplicate expense - Page become unresponsive when duplicating bulk expenses[/+] on Apr 23, 2026
  17. melvin-bot commented on Apr 23, 2026

    @melvin-bot

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

  18. melvin-bot commented on Apr 23, 2026

    @melvin-bot

    The solution for this issue has been 🚀 deployed to production 🚀 in version 9.3.61-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-30. 🎊

    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
  19. melvin-bot commented on Apr 23, 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