Skip to content

[Due for payment 2026-08-25] [$250] [Exploratory] 92500 - Expense -Deleted split moved from 1:1 chat to Self DM with negative amount after clear cache #92521

Closed

Description

@applause-bot

If you haven’t already, check out our contributing guidelines for onboarding and email [email protected] to request to join our Slack channel!


Version Number: 9.3.98-0
Reproducible in staging?: Yes
Reproducible in production?: Yes
If this was caught during regression testing, add the test name, ID and link from BrowserStack: #92351
Email or phone of affected tester (no customers): [email protected]
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.
Prerequisite 2: Have a private room created with at least one member invited.

  1. Open the Expensify app.
  2. Navigate to a private room conversation.
  3. Tap on "+" button > "Split Expense"
  4. Enter any amount and create the split expense.
  5. Navigate to "Account" > "Troubleshoot" > "Clear cache and restart"
  6. Return to "Inbox" and open the individual chat where split IOU was sent.
  7. Long tap on IOU preview and delete it.
  8. Navigate to Self DM.
  9. Note that split was moved to Self DM with negative amount.

Expected Result:

Split deleted from individual chat should not be moved to Self DM.

Actual Result:

Split deleted from individual chat after clearing cache, is moved to Self DM with negative amount.

Workaround:

Unknown

Platforms:

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

Screenshots/Videos

Bug7170547_1780495725855.ONX_5.mp4

View all open jobs on GitHub

Applause Internal Information
Exported by: Julia Ponikarchuk
Bug ID: 7170547

Upwork Automation - Do Not Edit
Issue OwnerCurrent Issue Owner: @marufsharifi

Activity

  1. applause-bot commented on Jun 3, 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/4b64440a329024262247648893a7eb64d3956bf21925fec104adc66b61bcddff

  2. added
    BugSomething is broken. Auto assigns a BugZero manager.
    on Jul 17, 2026
  3. MelvinBot commented on Jul 17, 2026

    @MelvinBot
    Contributor

    Proposal

    What is the root cause of that problem?

    Deleting a classic Split Bill from the 1:1 chat gets misrouted to the split-edit path instead of the delete path after a cache clear, because the code that tells a "bill split" apart from a newer "expense split" relies on optimistic Onyx data that isn't restored after "Clear cache and restart".

    When a Split Expense is created in a room, the flow builds one parent split transaction pinned to the synthetic SPLIT_REPORT_ID (-2) carrying comment.splits, plus one child transaction per participant in each 1:1 IOU, each stamped with comment.source = 'split' and comment.originalTransactionID = <parent id> (Split.ts:1440-1460, Split.ts:1758-1774, Split.ts:1906-1914). That parent transaction — and its comment.splits — exists only in optimistic data and is not re-hydrated from the server after a cache clear.

    On delete, useDeleteTransactions.ts:215-225 looks up the parent via allTransactions[originalTransactionID] (now undefined) and calls getOriginalTransactionWithSplitInfo. The only signal distinguishing a bill split from an expense split is the parent's comment.splits (TransactionUtils/index.ts:2894-2908). With the parent missing, isExpenseSplit hits its fallback and returns true for any child that has originalTransactionID + source === 'split' (TransactionUtils/index.ts:2873-2874). The classic split is therefore misclassified as an expense split and routed into updateSplitTransactions (the split re-build/restore path) instead of deleteMoneyRequest, which restores the surviving split portion onto the Self DM (where negative amounts are allowed) rather than deleting it.

    Evidence & data flow
    1. Create Split Expense in a room → parent txn on SPLIT_REPORT_ID (-2) with comment.splits; per-participant 1:1 children with comment.source='split' + comment.originalTransactionID (Split.ts:1440-1460, 1758-1774).
    2. Clear cache & restart → server re-hydrates the real 1:1 IOU + child, but not the transient -2 parent that held comment.splits.
    3. Delete the 1:1 preview → originalTransaction = allTransactions[originalTransactionID] is undefined (useDeleteTransactions.ts:215).
    4. isExpenseSplit(txn, undefined) returns true via the no-parent fallback (TransactionUtils/index.ts:2873-2874) — whereas with the parent present it would return !parent.comment.splits = false (2883).
    5. if (isExpenseSplit && originalTransactionID) sends it to the split-update branch instead of deleteMoneyRequest (useDeleteTransactions.ts:225) → the portion is restored on the Self DM with its negative sign.

    Confidence: Medium-High. The classification/misroute is verified in code end-to-end. Not confirmed against production logs, and the exact final amount-sign math in the restore path wasn't line-verified — but that is downstream of the misroute, which is the actual defect.

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

    Make the bill-split vs expense-split classification not depend on the optimistic-only -2 parent transaction, so a cache clear can't flip a classic split into the expense-split path:

    • In isExpenseSplit, the !originalTransaction fallback (TransactionUtils/index.ts:2873-2874) should not blindly treat "child has source==='split' but parent is missing" as an expense split. A missing parent is exactly the ambiguous case — default it to the non-destructive branch (treat as a bill split / route to deleteMoneyRequest) rather than the restore path.
    • Better, use a durable, server-persisted signal to tell the two split types apart instead of the transient comment.splits on the -2 parent (e.g. a persisted marker on the child, or the child's own report/type), so the decision is correct even before/without the parent being hydrated.

    Either way, deleting a classic split from the 1:1 chat should always go through deleteMoneyRequest and must never re-home the portion onto the Self DM.

    Related issues (same split / Self-DM delete area)

    These share the split ↔ Self-DM delete/restore machinery and may be resolved or affected by the same fix.


    Next Steps for Contributor+ team:
    To accept: @MelvinBot implement [this](https://github.com/Expensify/App/issues/92521) to create a draft PR.
    To refine: @MelvinBot <your feedback>
    To reject: Explain why you are rejecting Melvin's proposal.

  4. added
    ExternalAdded to denote the issue can be worked on by a contributor
    on Jul 17, 2026
  5. added
    Help WantedApply this label when an issue is open to proposals by contributors
    on Jul 17, 2026
  6. melvin-bot commented on Jul 17, 2026

    @melvin-bot

    Triggered auto assignment to Contributor-plus team member for initial proposal review - @sobitneupane (External)

  7. melvin-bot commented on Jul 17, 2026

    @melvin-bot
  8. 55 remaining items

  9. sobitneupane commented on Aug 25, 2026

    @sobitneupane
    Contributor

    @mallenexpensify @marufsharifi is the C+ of this issue. Could you please unassign me and assign it to @marufsharifi?

  10. melvin-bot commented on Aug 26, 2026

    @melvin-bot

    📣 @marufsharifi You have been assigned to this job!
    Please apply to the Upwork job and leave a comment on the Github issue letting us know when we can expect a PR to be ready for review 🧑‍💻
    Once you apply to this job, your Upwork ID will be stored and you will be automatically hired for future jobs!
    Keep in mind: Code of Conduct | Contributing 📖

  11. mallenexpensify commented on Aug 26, 2026

    @mallenexpensify
    Contributor

    Payment Summary

    Contributor: @lorretheboy paid $250 via Upwork
    Contributor+: @marufsharifi paid $250 via Upwork

    Both, can you please accept the job below? Please reply here and tag me once you have.
    https://www.upwork.com/jobs/~022092759056467726021

  12. marufsharifi commented on Aug 27, 2026

    @marufsharifi
    Contributor

    @mallenexpensify, I've accepted the offer. thanks.

  13. mallenexpensify commented on Aug 27, 2026

    @mallenexpensify
    Contributor

    @marufsharifi paid, summary updated above. Thx.

    @lorretheboy can you please accept the job below? Please reply here and tag me once you have.

    https://www.upwork.com/jobs/~022092759056467726021

  14. lorretheboy commented on Aug 30, 2026

    @lorretheboy
    Contributor

    @mallenexpensify I accepted it. Thank you

  15. mallenexpensify commented on Aug 31, 2026

    @mallenexpensify
    Contributor

    @lorretheboy paid, summary updated above. Thx.

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.DailyKSv2ExternalAdded to denote the issue can be worked on by a contributorNot a priority

Type

No type

Projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions