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
When creating an expense from FAB, going back from the Confirm details page to the amount step and next flow again takes the user to page not found instead of the confirmation page. #99429
Version Number: 9.4.57-3 Reproducible in staging?: Needs reproduction (Unable to reproduce) Reproducible in production?: Needs Reproduction If this was caught during regression testing, add the test name, ID and link from BrowserStack: Email or phone of affected tester (no customers): Logs:https://stackoverflow.com/c/expensify/questions/4856 Expensify/Expensify Issue URL: Issue reported by:@suneox Slack conversation (hyperlinked to channel name): #Expensify Bugs
Action Performed:
Log in with an account whose default workspace is a group (paid) workspace.
Confidence: Medium — root cause is pinned in code, but I couldn't confirm at runtime: the test account had no default group workspace, so my web repro took the self‑DM path (extra "Choose recipient" step) and correctly did not hit the bug. That's consistent with this being group‑workspace‑only.
What is the root cause of that problem?
The first Next and the second Next take different navigation branches inside submitGlobalCreate.
First Next → isReturningFromConfirmationPage is false (no participants yet) → navigateToDefaultWorkspace, which explicitly sets the draft's reportID + participants and navigates with a known‑goodtargetReport.reportID. → Confirm details opens ✅
When that lookup can't resolve a report after Back, chatReportID is undefined. navigateToExistingParticipantConfirmation passes it straight through, and ROUTES.MONEY_REQUEST_STEP_CONFIRMATION.getRoute has no guard for an undefined reportID — it builds .../confirmation/<transactionID>/undefined. The confirmation screen's report guard (withWritableReportOrNotFound / AccessOrNotFoundWrapper) then renders /not-found.
It only reproduces with a group (paid) default workspace because this whole path is gated on shouldUseDefaultExpensePolicy requiring a group policy; a personal default instead routes to the participant page, so the returning branch is never hit.
What changes do you think we should make in order to solve the problem?
In the returning‑from‑confirmation branch of submitGlobalCreate, when selectedReport / chatReportID can't be resolved, fall back to navigateToDefaultWorkspace (which re‑assigns participants + a valid reportID) instead of navigating with a stale/undefined reportID. As defense‑in‑depth, harden ROUTES.MONEY_REQUEST_STEP_CONFIRMATION.getRoute so it doesn't emit a route with an undefined/'undefined'reportID.
What alternative solutions did you explore? (Optional)
Confirming the exact failing value at runtime (whether transaction.reportID is undefined vs. a reportID missing from the report snapshot after Back) — worth a quick trace of transaction.reportID/chatReportID at IOUAmountSubmission.ts:530-536 with a group‑workspace account.
Next Steps for Contributor+ team: To accept:@MelvinBot implement [this](https://github.com/Expensify/App/issues/99429) to create a draft PR. To refine:@MelvinBot <your feedback> To reject: Explain why you are rejecting Melvin's proposal.
I can reproduce it easily. chatReportID of the WS chat is an empty string after resetting the cache (result from OpenApp API). After going to the confirmation page, transaction?.reportID is the policy expense chat; when going back to the amount page and clicking the Next button, the chatReportID here is an empty string, which makes the route invalid and /not-found appears. We should use || operator instead of ?? to fix the case chatReportID is an empty string, or only use chatReport if the selectedReport is money request report (expense or iou)
The second Next after Back reaches the returning-from-confirmation branch of submitGlobalCreate (IOUAmountSubmission.ts:529-536), which builds the confirmation route's :reportID segment from selectedReport?.chatReportID ?? selectedReport?.reportID (IOUAmountSubmission.ts:534).
When the default workspace is a paid group workspace and its expense chat currently holds an open expense report, the server sends that chat's chatReportID down as the empty string "". ?? only falls back for null and undefined, not for "", so line 534 yields "" and the route becomes create/submit/confirmation/<transactionID>/ with an empty segment. No screen route matches that, and the flow lands on /not-found. Measured on a real account: the key is absent before an expense exists, becomes "" after an open expense report is created and the account reloads, and disappears again once the expense is deleted. The optimistic write after Create expense does not carry the field, which is why a fresh-session repro does not trigger it.
With newManualExpenseFlow on, the amount step no longer mounts for create, so this branch is unreachable. Accounts outside that beta still run the old amount-first flow and still hit the empty segment, so the guard is still needed.
What changes do you think we should make in order to solve the problem?
Make line 534 treat an empty string as missing, since "" is never a valid report ID:
For the reported case the empty chatReportID falls back to the expense chat's own reportID, producing a valid create/submit/confirmation/<transactionID>/<reportID> and the confirmation page renders as expected.
For every existing case where chatReportID holds a real value the behavior is identical to today, and the first-Next path (navigateToDefaultWorkspace) is untouched.
Add a case to the existing submitGlobalCreate suite in tests/unit/IOUAmountSubmissionTest.ts: a selected report with chatReportID: '' and a real reportID, asserting the confirmation route is built from the reportID and not an empty segment.
What alternative solutions did you explore? (Optional)
Resolve the bound report through isMoneyRequestReport and getReportOrReportDraftForAmount before reading an ID, with a navigateToDefaultWorkspace fallback (IOUAmountSubmission.ts:525) when nothing resolves. Also correct and it covers a changed-recipient edge case, but it is a larger change than the single empty-string bug that produces this /not-found.
Appending ?? iouReportID to the chain. Does not fix the report: ?? leaves "" untouched, and the report's failure is exactly the empty string.
Reminder: Please use plain English, be brief and avoid jargon. Feel free to use images, charts or pseudo-code if necessary. Do not post large multi-line diffs or write walls of text. Do not create PRs unless you have been hired for this job.
@FitseTLTProposal updated — not reproducible with the beta on (INVOICE keeps the amount step but never enters this branch, and the embedded confirmation's amount edit returns on backTo before submitGlobalCreate), still live without it on 7df2d24b79.
I also measured the route Navigation.navigate receives for the four candidate derivations across five report shapes: ||, getNonEmptyStringOnyxID(...) and the combined guard print identical routes and all send an expense report with an empty chatReportID to that report's own ID, which is not a chat — the type check alone changes exactly the reported row.
@FitseTLT this issue was created 2 weeks ago. Are we close to approving a proposal? If not, what's blocking us from getting this issue assigned? Don't hesitate to create a thread in #expensify-open-source to align faster in real time. Thanks!
Proposal updated. @FitseTLT which branch are you testing on? That decides whether the steps exist at all.
On main and staging the second Next is gone: #100215 (merged 2026-09-09) removed the newManualExpenseFlow flag, so shouldEmbedConfirmation is now shouldUseTab || iouType === PAY and the Manual tab renders the confirmation inline for create. submitGlobalCreate is untouched, but the FAB → Create expense → Manual path no longer has a second Next to press.
Production still has the gate, and there it needs three things at once:
An account not on the newManualExpenseFlow beta.
The default expense policy set to a group workspace ("Set as default workspace").
An open expense report on that workspace's expense chat: create one expense on it and leave the report unsubmitted, then reload. Before the reload the chat's chatReportID key is absent; after it the server sends chatReportID: "", and that empty string is what reaches the route.
Then FAB → Create expense → Manual → amount → Next → Back → Next lands on /not-found. Same account, same steps with only that field cleared in Onyx: Confirm details. Recorded on a web dev build with production's gating (submitGlobalCreate byte-identical to main today), URL bar visible, one take: bug at 0:42, control at 1:28. Setting up step 3 on camera: setup clip. The "beta off" state is simulated by removing the beta from Onyx on the test account, which is otherwise on the beta; that is stated on screen.
Correction to my 09-06 note: deleting the expense only clears the field when no other open expense remains on that chat.
Activity
exfy-zapier commentedon Aug 25, 2026
This has been labelled "Needs Reproduction". Follow the steps here: https://stackoverflowteams.com/c/expensify/questions/16989
MelvinBot commentedon Aug 25, 2026
Proposal
What is the root cause of that problem?
The first Next and the second Next take different navigation branches inside
submitGlobalCreate.isReturningFromConfirmationPageisfalse(no participants yet) →navigateToDefaultWorkspace, which explicitly sets the draft'sreportID+ participants and navigates with a known‑goodtargetReport.reportID. → Confirm details opens ✅chatReportID = selectedReport?.chatReportID ?? selectedReport?.reportID, whereselectedReport = getReportOrReportDraftForAmount(transaction.reportID).When that lookup can't resolve a report after Back,
chatReportIDisundefined.navigateToExistingParticipantConfirmationpasses it straight through, andROUTES.MONEY_REQUEST_STEP_CONFIRMATION.getRoutehas no guard for an undefinedreportID— it builds.../confirmation/<transactionID>/undefined. The confirmation screen's report guard (withWritableReportOrNotFound/AccessOrNotFoundWrapper) then renders/not-found.It only reproduces with a group (paid) default workspace because this whole path is gated on
shouldUseDefaultExpensePolicyrequiring a group policy; a personal default instead routes to the participant page, so the returning branch is never hit.What changes do you think we should make in order to solve the problem?
In the returning‑from‑confirmation branch of
submitGlobalCreate, whenselectedReport/chatReportIDcan't be resolved, fall back tonavigateToDefaultWorkspace(which re‑assigns participants + a validreportID) instead of navigating with a stale/undefinedreportID. As defense‑in‑depth, hardenROUTES.MONEY_REQUEST_STEP_CONFIRMATION.getRouteso it doesn't emit a route with anundefined/'undefined'reportID.What alternative solutions did you explore? (Optional)
Confirming the exact failing value at runtime (whether
transaction.reportIDisundefinedvs. a reportID missing from the report snapshot after Back) — worth a quick trace oftransaction.reportID/chatReportIDatIOUAmountSubmission.ts:530-536with a group‑workspace account.Related issues
truph01). Same returning‑from‑confirmation branch, P2P variant → nav loop instead of/not-found. A fix should cover both.Next Steps for Contributor+ team:
To accept:
@MelvinBot implement [this](https://github.com/Expensify/App/issues/99429)to create a draft PR.To refine:
@MelvinBot <your feedback>To reject: Explain why you are rejecting Melvin's proposal.
view run
melvin-bot commentedon Aug 25, 2026
Triggered auto assignment to Contributor-plus team member for initial proposal review - @FitseTLT (
External)melvin-bot commentedon Aug 25, 2026
Unable to auto-create job on Upwork. The BZ team member should create it manually for this issue.
suneox commentedon Aug 25, 2026
@FitseTLT if you can't reproduce it, I can take over this issue to verify the solution
mukhrr commentedon Aug 26, 2026
i can reproduce this as well
nkdengineer commentedon Aug 26, 2026
I can reproduce it easily.
chatReportIDof the WS chat is an empty string after resetting the cache (result fromOpenAppAPI). After going to the confirmation page,transaction?.reportIDis the policy expense chat; when going back to the amount page and clicking the Next button, thechatReportIDhere is an empty string, which makes the route invalid and/not-foundappears. We should use||operator instead of??to fix the casechatReportIDis an empty string, or only usechatReportif theselectedReportis money request report (expenseoriou)App/src/libs/IOUAmountSubmission.ts
Line 534 in 6d0c60d
Screen.Recording.2026-08-26.at.16.06.31.mov
FitseTLT commentedon Aug 28, 2026
🆘 C+ Help Wanted
35 remaining items
abdullahitijani commentedon Sep 6, 2026
Proposal
What is the root cause of that problem?
The second Next after Back reaches the returning-from-confirmation branch of
submitGlobalCreate(IOUAmountSubmission.ts:529-536), which builds the confirmation route's:reportIDsegment fromselectedReport?.chatReportID ?? selectedReport?.reportID(IOUAmountSubmission.ts:534).When the default workspace is a paid group workspace and its expense chat currently holds an open expense report, the server sends that chat's
chatReportIDdown as the empty string"".??only falls back fornullandundefined, not for"", so line 534 yields""and the route becomescreate/submit/confirmation/<transactionID>/with an empty segment. No screen route matches that, and the flow lands on/not-found. Measured on a real account: the key is absent before an expense exists, becomes""after an open expense report is created and the account reloads, and disappears again once the expense is deleted. The optimistic write after Create expense does not carry the field, which is why a fresh-session repro does not trigger it.With
newManualExpenseFlowon, the amount step no longer mounts for create, so this branch is unreachable. Accounts outside that beta still run the old amount-first flow and still hit the empty segment, so the guard is still needed.What changes do you think we should make in order to solve the problem?
Make line 534 treat an empty string as missing, since
""is never a valid report ID:const chatReportID = selectedReport?.chatReportID || selectedReport?.reportID;chatReportIDfalls back to the expense chat's ownreportID, producing a validcreate/submit/confirmation/<transactionID>/<reportID>and the confirmation page renders as expected.chatReportIDholds a real value the behavior is identical to today, and the first-Next path (navigateToDefaultWorkspace) is untouched.Add a case to the existing
submitGlobalCreatesuite intests/unit/IOUAmountSubmissionTest.ts: a selected report withchatReportID: ''and a realreportID, asserting the confirmation route is built from thereportIDand not an empty segment.What alternative solutions did you explore? (Optional)
isMoneyRequestReportandgetReportOrReportDraftForAmountbefore reading an ID, with anavigateToDefaultWorkspacefallback (IOUAmountSubmission.ts:525) when nothing resolves. Also correct and it covers a changed-recipient edge case, but it is a larger change than the single empty-string bug that produces this/not-found.?? iouReportIDto the chain. Does not fix the report:??leaves""untouched, and the report's failure is exactly the empty string.Reminder: Please use plain English, be brief and avoid jargon. Feel free to use images, charts or pseudo-code if necessary. Do not post large multi-line diffs or write walls of text. Do not create PRs unless you have been hired for this job.
melvin-bot commentedon Sep 7, 2026
@FitseTLT Uh oh! This issue is overdue by 2 days. Don't forget to update your issues!
yusufdeveloper2903 commentedon Sep 8, 2026
@FitseTLT Proposal updated — not reproducible with the beta on (
INVOICEkeeps the amount step but never enters this branch, and the embedded confirmation's amount edit returns onbackTobeforesubmitGlobalCreate), still live without it on7df2d24b79.I also measured the route
Navigation.navigatereceives for the four candidate derivations across five report shapes:||,getNonEmptyStringOnyxID(...)and the combined guard print identical routes and all send an expense report with an emptychatReportIDto that report's own ID, which is not a chat — the type check alone changes exactly the reported row.melvin-bot commentedon Sep 8, 2026
@FitseTLT this issue was created 2 weeks ago. Are we close to approving a proposal? If not, what's blocking us from getting this issue assigned? Don't hesitate to create a thread in #expensify-open-source to align faster in real time. Thanks!
melvin-bot commentedon Sep 9, 2026
@FitseTLT Huh... This is 4 days overdue. Who can take care of this?
FitseTLT commentedon Sep 10, 2026
Not able to reproduce
trasnake87 commentedon Sep 11, 2026
Proposal updated. @FitseTLT which branch are you testing on? That decides whether the steps exist at all.
On
mainandstagingthe second Next is gone: #100215 (merged 2026-09-09) removed thenewManualExpenseFlowflag, soshouldEmbedConfirmationis nowshouldUseTab || iouType === PAYand the Manual tab renders the confirmation inline forcreate.submitGlobalCreateis untouched, but the FAB → Create expense → Manual path no longer has a second Next to press.Production still has the gate, and there it needs three things at once:
newManualExpenseFlowbeta.chatReportIDkey is absent; after it the server sendschatReportID: "", and that empty string is what reaches the route.Then FAB → Create expense → Manual → amount → Next → Back → Next lands on
/not-found. Same account, same steps with only that field cleared in Onyx: Confirm details. Recorded on a web dev build with production's gating (submitGlobalCreatebyte-identical to main today), URL bar visible, one take: bug at 0:42, control at 1:28. Setting up step 3 on camera: setup clip. The "beta off" state is simulated by removing the beta from Onyx on the test account, which is otherwise on the beta; that is stated on screen.Correction to my 09-06 note: deleting the expense only clears the field when no other open expense remains on that chat.
melvin-bot commentedon Sep 15, 2026
@FitseTLT Huh... This is 4 days overdue. Who can take care of this?
trjExpensify commentedon Sep 17, 2026
Closing as the
newManualExpenseFlowbeta has been deprecated, so it's the "new normal".