Skip to content

Expense - Duplicated expense is created in Self-DM #100642

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.74-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: #98960
Email or phone of affected tester (no customers): jose.sat94+10jul@gmail.com
Issue reported by: Applause Internal Team
Bug source: Pull Request QA execution
Device used: MacBook Pro/13.7.8, Samsung Galaxy S21/Android 15, iPhone 13/18.6.2
App Component: Other

Action Performed:

  1. Launch Expensify app
  2. Log in with valid credentials
  3. Open Self-DM
  4. Click the "+" button → select "Create expense"
  5. Enter an amount → click Next
  6. Fill in merchant/description → click Create expense
  7. Click More dropdown and select "Duplicated expense"

Expected Result:

The duplicated expense is created in the workspace chat

Actual Result:

The duplicated expense is created in the Self-DM instead of the workspace chat

Workaround:

Unknown

Platforms:

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

Screenshots/Videos

Bug7254519_1788898948744.1-web.mp4

View all open jobs on GitHub

Applause Internal Information
Exported by: Svetlana Lazutkina
Bug ID: 7254519

Issue OwnerCurrent Issue Owner: @daledah

Activity

  1. added
    BugSomething is broken. Auto assigns a BugZero manager.
    on Sep 8, 2026
  2. applause-bot commented on Sep 8, 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/379dc2ab89c17a3252437b5b94ab130128013e897507a3b88bea30861c791d15

  3. MelvinBot commented on Sep 8, 2026

    @MelvinBot
    Contributor

    Proposal

    This is not a bug — the duplicate landing in the Self-DM is the intended behavior, and PR #98960 did not cause it. The "Expected Result" in this report (and step 7 of that PR's test steps) was written against behavior that #98396 deliberately changed on 2026-08-21.

    What is the root cause of that problem?

    A Self-DM expense is unreported (transaction.reportID === '0'), and duplicateExpenseTransaction now intentionally mirrors the source: an unreported source duplicates as unreported, even when the user has a workspace.

    The rule is stated in the code comment itself — src/libs/actions/IOU/Duplicate.ts:853-855:

    // A duplicate mirrors the source, so an unreported source stays unreported even when a workspace is available
    const isSourceUnreported = !transaction.reportID || transaction.reportID === CONST.REPORT.UNREPORTED_REPORT_ID;

    That flag routes the duplicate to trackExpense instead of requestMoney at Duplicate.ts:903, which puts the copy back in the Self-DM. A unit test asserts exactly this: tests/actions/IOUTest/DuplicateTest.ts:2383 — "should call trackExpense when the source expense is unreported even though a targetPolicy is provided".

    #98960 only threaded an already-false value (isDraftChatReport: false) through the same call. It is a no-op for this flow, which is why the report reproduces on production too — #98396 shipped 18 days earlier.

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

    No App code change. Instead:

    1. Close this issue as not-a-bug.
    2. Correct step 7 of refactor: remove isDraftReport and require isDraftChatReport in the track-expense flow #98960's test steps to: "Verify the duplicated expense is created in the Self-DM and stays unreported." Leaving it as-is will keep generating this same QA report.

    If product has since decided a Self-DM duplicate should land in the workspace chat, that is a new product change against #98396, not a fix here — and it needs backend coordination, because the same rule is enforced server-side (Web-Expensify counterpart of that PR). Flagging that for an internal engineer rather than proposing an App-only patch.

    What alternative solutions did you explore? (Optional)

    Evidence chain and alternatives ruled out

    Why a Self-DM expense is unreported. In getTrackExpenseInformation, shouldUseMoneyReport is false for a Self-DM chat (TrackExpense.ts:1051), so the transaction is written with reportID: CONST.REPORT.UNREPORTED_REPORT_ID (TrackExpense.ts:1117). That makes isSourceUnreported true.

    Ruled out: "targetPolicy is missing, so the workspace branch is unreachable." Every call site passes the default workspace and its expense chat — useExpenseActions.ts:275-277, MoneyRequestHeaderSecondaryActions.tsx:288-290, useBulkDuplicateAction.ts:88-91. For a user with a workspace, targetPolicy is defined. isSourceUnreported is what routes to the Self-DM.

    Ruled out: PR #98960 regressed it. That PR replaced isDraftChatReport ?? isDraftReport(chatReport?.reportID) with the explicit param. In the duplicate flow report is undefined, so chatReport resolves to the Self-DM, and REPORT_DRAFT Onyx entries are only ever written for draft workspace chats (via saveReportDraft in the workspace-creation flows). The old fallback evaluated to false — identical to the new explicit false. Its only effect is gating the buildPolicyData workspace-creation block, which was skipped before and is skipped now.

    Timeline. #98396 "Keep a duplicated expense unreported when the source is unreported" merged 2026-08-21 (touched Duplicate.ts + DuplicateTest.ts). #98189 later moved the comment/const but did not change routing. #98960 merged 2026-09-08, hours before this report.

    Known design smell (not this bug). reportID === '0' cannot distinguish "expense lives in a Self-DM" from "workspace expense that was unreported", and shouldDuplicateSelfDMExpense also feeds buildDuplicateTransactionParams/getDuplicateRequestType, which downgrade GPS distance to manual distance. Any future change to the routing rule should split those two concerns into separate booleans.

    Web reproduction (dev NewDot)

    I reproduced the flow: created a $25 "Coffee Shop" expense in the Self-DM, then More → Duplicate expense. The duplicate was created in the same Self-DM — its Report field shows the Self-DM, not a workspace chat.

    Self-DM thread showing original and duplicate expenses Duplicate expense detail view; Report field shows the Self-DM

    Caveat, stated plainly: the test account had no workspaces (screenshot), so this run only exercises the !targetPolicy half of the condition. The isSourceUnreported half — a workspace exists and the duplicate still stays in the Self-DM — is covered by the unit test at DuplicateTest.ts:2383 rather than by my run. No console errors.

    Possibly related issues

    Neither is a duplicate of this report.


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


    view run · no recording available

  4. added
    ExternalAdded to denote the issue can be worked on by a contributor
    on Sep 8, 2026
  5. melvin-bot commented on Sep 8, 2026

    @melvin-bot

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

  6. melvin-bot commented on Sep 8, 2026

    @melvin-bot

    Unable to auto-create job on Upwork. The BZ team member should create it manually for this issue.

  7. mountiny commented on Sep 9, 2026

    @mountiny
    Contributor

    @daledah this looks like a follow up to your PR, can you take a look please

  8. daledah commented on Sep 10, 2026

    @daledah
    Contributor

    duplicateExpenseTransaction chose trackExpense vs requestMoney from targetPolicy alone, and every caller passes the default workspace, so duplicating an unreported expense always put the copy on a report. It now checks the source too, so an unreported source stays unreported. The backend half is https://github.com/Expensify/Web-Expensify/pull/55303.

    @mountiny This isn't a regression from my PR - it's the intended behavior of #98396.

    I think we can close this as expected behavior

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

Type

No type

Projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions