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
Expense - Duplicated expense is created in Self-DM #100642
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:
Launch Expensify app
Log in with valid credentials
Open Self-DM
Click the "+" button → select "Create expense"
Enter an amount → click Next
Fill in merchant/description → click Create expense
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
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.
// A duplicate mirrors the source, so an unreported source stays unreported even when a workspace is availableconstisSourceUnreported=!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?
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: 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.
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.
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.
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.
Activity
applause-bot commentedon Sep 8, 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/379dc2ab89c17a3252437b5b94ab130128013e897507a3b88bea30861c791d15
MelvinBot commentedon Sep 8, 2026
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'), andduplicateExpenseTransactionnow 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:That flag routes the duplicate to
trackExpenseinstead ofrequestMoneyatDuplicate.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-
falsevalue (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:
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-Expensifycounterpart 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,shouldUseMoneyReportis false for a Self-DM chat (TrackExpense.ts:1051), so the transaction is written withreportID: CONST.REPORT.UNREPORTED_REPORT_ID(TrackExpense.ts:1117). That makesisSourceUnreportedtrue.Ruled out: "
targetPolicyis 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,targetPolicyis defined.isSourceUnreportedis 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 flowreportisundefined, sochatReportresolves to the Self-DM, andREPORT_DRAFTOnyx entries are only ever written for draft workspace chats (viasaveReportDraftin the workspace-creation flows). The old fallback evaluated tofalse— identical to the new explicitfalse. Its only effect is gating thebuildPolicyDataworkspace-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", andshouldDuplicateSelfDMExpensealso feedsbuildDuplicateTransactionParams/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
Reportfield shows the Self-DM, not a workspace chat.Caveat, stated plainly: the test account had no workspaces (screenshot), so this run only exercises the
!targetPolicyhalf of the condition. TheisSourceUnreportedhalf — a workspace exists and the duplicate still stays in the Self-DM — is covered by the unit test atDuplicateTest.ts:2383rather than by my run. No console errors.Possibly related issues
[Exploratory] Expense - Duplicate negative deleted manual expense appear positive in Workspace chat(also duplicate-routing behavior, different symptom)[$125] [Exploratory] 91651-GPS-Duplicating GPS expense shows unexpected error(touches the samegetDuplicateRequestTypeGPS→manual path)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
melvin-bot commentedon Sep 8, 2026
Triggered auto assignment to Contributor-plus team member for initial proposal review - @brunovjk (
External)melvin-bot commentedon Sep 8, 2026
Unable to auto-create job on Upwork. The BZ team member should create it manually for this issue.
mountiny commentedon Sep 9, 2026
@daledah this looks like a follow up to your PR, can you take a look please
daledah commentedon Sep 10, 2026
@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