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-08-27] Expense -Deleted split remains in Self DM with infinite loading after being deleted on Spend #97413
Version Number: 9.4.46-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:#92055 Email or phone of affected tester (no customers):ibellicotest+2766@gmail.com Issue reported by: Applause Internal Team Bug source: Exploratory - Significant User Experience Deterioration Device used: Motorola MotoG60 / Android 12 (Hybrid app) - Windows 11 / Chrome App Component: Money Requests
Action Performed:
Prerequisite: Account has Self DM.
Open the staging.new.expensify.com website.
Navigate to Self DM.
Create a manual expense.
Navigate to "Spend" > "Expenses"
Select the just created expense.
Click on dropdown menu > "Split" > Save splits.
Select one of the splits and delete it.
Select the remaining split and delete it too.
Navigate to Self DM.
Note that an expense preview with infinite loading is visible.
Open the expense.
Note that "It´s not here" page opens.
Return to chat and delete expense.
Note that now, expense disappears.
Expected Result:
Split deleted while on "Spend", should automatically disappear from chat too.
Actual Result:
Split deleted while on "Spend", remains in Self DM showing infinite loading on preview and leads to "It´s not here" page.
👋 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: #92055 - "Fix bugs for selfDM splits feature" by @ZhenjaHorbach (High confidence) Related Issues: N/A
Recommendation: ROLL FORWARD (targeted fix)
The orphaned Self DM preview is pure client-side optimistic-Onyx cleanup logic that PR #92055 restructured; reverting is unsafe because that PR bundles fixes for 8 separate self-DM-split issues (#91874, #91876, #91879, #91907, #91913, #91917, #91918, #91920), so a revert would reintroduce all of them. A targeted fix in useDeleteTransactions is smaller and lower-risk.
Assigned: @ZhenjaHorbach (PR author), @lakchote and @dmkt9 (approving reviewers) — already assigned by melvin-bot Labels: No change. DeployBlockerCash is kept (App-side fix, and the issue is a new feature that is N/A in production). DeployBlocker was not present.
📋 Detailed Analysis
Evidence
The bug is N/A in production and only reproduces on staging — this is a brand-new feature path (splitting a Self DM expense, then deleting the split(s) from the "Spend" > Expenses / Search view). There is no server error involved; the visible symptom is an optimistic Onyx report preview that is never cleaned up (infinite-loading preview → "It's not here"), which is entirely front-end state.
PR Fix bugs for selfDM splits feature #92055 is the offending PR flagged by melvin-bot and is also the link given for "caught during regression testing." Confirming what it changed: it heavily restructured exactly the two files that own this flow:
src/hooks/useDeleteTransactions.ts — the delete-from-Search classification logic (added isMovedExpenseSplitChild, TransactionWithAction, changed how split children are grouped by original transaction).
src/libs/actions/IOU/SplitTransactionUpdate.ts — the reverse-split / report-action cleanup logic (firstIOU → iouActionsToCleanUp loop that marks the original IOU action DELETE, the isLastTransactionInReport report teardown, and the snapshot rescaling).
Because the affected code is the delete + Self-DM-cleanup path that this PR rewrote, and the feature does not exist in prod, this PR is the source of the regression.
Verification
Affected files (confirmed present in the merged code at f704af6):
Confirmed the manual-expense repro does not hit the per-diem "redirect to split-edit" branch: shouldRedirectDeleteToSplitExpenseEdit requires isPerDiemRequest, so a manual split falls through to the normal delete path below.
Confirmed on staging (isProduction === false) getChildTransactionsincludes the unreported Self-DM split children (the isProduction guard that would exclude them is skipped), so the delete path treats them as split children.
Root Cause
When splits are deleted from the "Spend"/Search view, deleteTransactions groups the Self-DM split children under their originalTransactionID. In the per-original loop, when every remaining child of an original is being removed, the code takes this early branch:
Those children are then deleted one-by-one via the plain deleteMoneyRequest loop, which only tears down each split's own transaction + transaction thread (action.childReportID).
Crucially, this branch bypasses updateSplitTransactions — the only code that knows how to reconcile the original (container) transaction. When a Self-DM expense is split, the original transaction is moved into the hidden SPLIT_REPORT_ID container and its Self-DM IOU/report-preview action is what the user still sees in chat. The container/preview is only ever cleaned up (marked DELETE, report torn down) inside updateSplitTransactions via the iouActionsToCleanUp and isLastTransactionInReport logic. Because deleting the last child skips that function entirely, the original container transaction and its Self DM report preview are left orphaned: the preview references a transaction that no longer resolves, producing the infinite loading spinner and the "It's not here" page. Manually deleting the leftover preview afterward is what finally clears it (matching the reported workaround at step 13-14).
In short: the childTransactions.length === 0 shortcut deletes the leaf split transactions but never restores-and-deletes (or directly cleans up) the original Self-DM split container + its report-preview action.
Suggested fix direction (for the assignees)
When childTransactions.length === 0 for an original that is a Self-DM split container, route the deletion through the split reconciliation path (updateSplitTransactions / reverse-split teardown) so the original container transaction is removed and its Self-DM report-preview action is marked DELETE, instead of falling back to per-child deleteMoneyRequest. This mirrors the cleanup already applied when at least one child survives.
The solution for this issue has been 🚀 deployed to production 🚀 in version 9.4.56-3 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-27. 🎊
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 [-]Expense -Deleted split remains in Self DM with infinite loading after being deleted on Spend[/-][+][Due for payment 2026-08-27] Expense -Deleted split remains in Self DM with infinite loading after being deleted on Spend[/+]on Aug 20, 2026
Activity
applause-bot commentedon Jul 30, 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/a978ba3b6cb2a49094d9f5675bec46914466e0a67ca29b3c3a58b1ceab0021cb
melvin-bot commentedon Jul 30, 2026
You have been assigned to this deploy blocker because you recently merged this PR: #92055
melvin-bot commentedon Jul 30, 2026
@lakchote @dmkt9 you have been assigned to this deploy blocker because you reviewed the offending PR: #92055
melvin-bot commentedon Jul 30, 2026
@ZhenjaHorbach you have been assigned to this deploy blocker because you authored the offending PR: #92055
melvin-bot commentedon Jul 30, 2026
💬 A slack conversation has been started in #expensify-open-source
github-actions commentedon Jul 30, 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 30, 2026
🔍 Investigation Summary
Classification: Frontend bug
Causing PR: #92055 - "Fix bugs for selfDM splits feature" by @ZhenjaHorbach (High confidence)
Related Issues: N/A
Recommendation: ROLL FORWARD (targeted fix)
The orphaned Self DM preview is pure client-side optimistic-Onyx cleanup logic that PR #92055 restructured; reverting is unsafe because that PR bundles fixes for 8 separate self-DM-split issues (#91874, #91876, #91879, #91907, #91913, #91917, #91918, #91920), so a revert would reintroduce all of them. A targeted fix in
useDeleteTransactionsis smaller and lower-risk.Assigned: @ZhenjaHorbach (PR author), @lakchote and @dmkt9 (approving reviewers) — already assigned by melvin-bot
Labels: No change.
DeployBlockerCashis kept (App-side fix, and the issue is a new feature that is N/A in production).DeployBlockerwas not present.📋 Detailed Analysis
Evidence
src/hooks/useDeleteTransactions.ts— the delete-from-Search classification logic (addedisMovedExpenseSplitChild,TransactionWithAction, changed how split children are grouped by original transaction).src/libs/actions/IOU/SplitTransactionUpdate.ts— the reverse-split / report-action cleanup logic (firstIOU→iouActionsToCleanUploop that marks the original IOU actionDELETE, theisLastTransactionInReportreport teardown, and the snapshot rescaling).Verification
f704af6):src/hooks/useDeleteTransactions.tssrc/libs/actions/IOU/SplitTransactionUpdate.tsgh pr view 92055 --json files). Merge commit2f02df8381ffcea859f6546963ac6ef1d2e12c21.shouldRedirectDeleteToSplitExpenseEditrequiresisPerDiemRequest, so a manual split falls through to the normal delete path below.isProduction === false)getChildTransactionsincludes the unreported Self-DM split children (theisProductionguard that would exclude them is skipped), so the delete path treats them as split children.Root Cause
When splits are deleted from the "Spend"/Search view,
deleteTransactionsgroups the Self-DM split children under theiroriginalTransactionID. In the per-original loop, when every remaining child of an original is being removed, the code takes this early branch:src/hooks/useDeleteTransactions.ts#L252-L255Those children are then deleted one-by-one via the plain
deleteMoneyRequestloop, which only tears down each split's own transaction + transaction thread (action.childReportID).Crucially, this branch bypasses
updateSplitTransactions— the only code that knows how to reconcile the original (container) transaction. When a Self-DM expense is split, the original transaction is moved into the hiddenSPLIT_REPORT_IDcontainer and its Self-DM IOU/report-preview action is what the user still sees in chat. The container/preview is only ever cleaned up (markedDELETE, report torn down) insideupdateSplitTransactionsvia theiouActionsToCleanUpandisLastTransactionInReportlogic. Because deleting the last child skips that function entirely, the original container transaction and its Self DM report preview are left orphaned: the preview references a transaction that no longer resolves, producing the infinite loading spinner and the "It's not here" page. Manually deleting the leftover preview afterward is what finally clears it (matching the reported workaround at step 13-14).In short: the
childTransactions.length === 0shortcut deletes the leaf split transactions but never restores-and-deletes (or directly cleans up) the original Self-DM split container + its report-preview action.Suggested fix direction (for the assignees)
When
childTransactions.length === 0for an original that is a Self-DM split container, route the deletion through the split reconciliation path (updateSplitTransactions/ reverse-split teardown) so the original container transaction is removed and its Self-DM report-preview action is markedDELETE, instead of falling back to per-childdeleteMoneyRequest. This mirrors the cleanup already applied when at least one child survives.5 remaining items
melvin-bot commentedon Aug 19, 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 Aug 19, 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 Aug 20, 2026
@ZhenjaHorbach / @dmkt9
The solution for this issue has been 🚀 deployed to production 🚀 in version 9.4.56-3 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-27. 🎊
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]
[-]Expense -Deleted split remains in Self DM with infinite loading after being deleted on Spend[/-][+][Due for payment 2026-08-27] Expense -Deleted split remains in Self DM with infinite loading after being deleted on Spend[/+]