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
👋 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: #84658 - "Fix manual distance expense from QAB showing amount as zero" by @MelvinBot (High confidence) Related Issues: #82801
Recommendation: ROLL FORWARD
The fix in PR #84658 addressed a critical issue (manual distance expenses showing $0.00), but appears to have introduced a regression. Reverting would reintroduce the original zero-amount bug, which is also a deploy blocker. A roll-forward fix is needed to address the unexpected error while preserving the amount calculation logic.
Labels: DeployBlockerCash will be kept (frontend bug blocks App deploy). DeployBlocker will be removed (no backend changes needed).
📋 Detailed Analysis
Evidence
PR #84658 was merged on March 12, 2026 and is included in this staging deploy (v9.3.37-0). The PR modified src/libs/actions/IOU/MoneyRequest.ts to calculate the amount for manual distance expenses when shouldSkipConfirmation is true (QAB flow).
✓ The changes relate to manual distance expense creation
✓ The PR was merged on March 12, aligned with when the staging deploy was cut
✓ Issue is reproducible on staging but NOT on production (confirming it's from a PR in this deploy)
Root Cause
The DistanceRequestUtils.getRate() function or one of its dependencies (getMileageRates, getDefaultMileageRate, getPersonalPolicy) may be throwing an error or returning unexpected values in certain scenarios, such as:
Policy not loaded: When creating a distance expense quickly after login, the policy data (including customUnits and distance rates) may not be fully loaded from Onyx yet
Missing distance rates: If the policy doesn't have distance rates configured, getDefaultMileageRate may return undefined, causing issues downstream
Personal policy unavailable: The getPersonalPolicy() function accesses allPolicies which is populated asynchronously via Onyx.connect, potentially returning undefined if called too early
The original issue #82801 was specifically about the QAB flow with offline creation, but the fix introduced new logic that runs synchronously and may not handle all edge cases gracefully.
Please re-state the problem that we are trying to solve in this issue.
Creating a manual distance expense (from a workspace chat or 1:1 chat) shows an "unexpected error" on staging V9.3.37-0 (Android). The error appears a few seconds after creating the expense, when the user opens it. This does not reproduce in production, indicating it is a regression from a PR in the current staging build.
What is the root cause of that problem?
The primary suspect is PR #84658 ("Fix manual distance expense from QAB showing amount as zero"), which modified src/libs/actions/IOU/MoneyRequest.ts:645-662 in the handleMoneyRequestStepDistanceNavigation function's skip-confirmation path.
What changed: Previously, the skip-confirmation path hardcoded amount: 0 and merchant: translate('iou.fieldPending') for manual distance expenses. PR #84658 replaced this with computed values using DistanceRequestUtils.getRate(), getDistanceRequestAmount(), and getDistanceMerchant() to calculate a real amount and merchant string from the distance and mileage rate.
How the error surfaces: The error message displayed is iou.error.genericCreateFailureMessage = "Unexpected error submitting this expense. Please try again later." This is set via failureData in buildOnyxDataForMoneyRequest (src/libs/actions/IOU/index.ts:2391-2418), which fires when the backend API (TrackExpense or CreateDistanceRequest) returns an error response. The error shows as a red brick road (RBR) indicator on the transaction preview and as an error message when opening the transaction thread.
Why it fails: The trackExpense API path (used for TRACK-type expenses in 1:1 chats) now sends a calculated amount instead of 0. Additionally, the merchant value changed from "Pending" to a computed distance merchant string (e.g., "20 mi @ $0.67 / mi"). The backend may be rejecting the request due to the changed parameter values, or there may be an edge case in the DistanceRequestUtils.getRate() call when transaction/policy data isn't fully loaded during the fast skip-confirmation flow.
A revert PR (#85157) already exists for PR #84658, confirming it has been identified as problematic.
Note: For the createDistanceRequest path (used for SUBMIT-type expenses in workspace chats), the amount is not sent as an API parameter — the backend calculates it independently. If the error also occurs in workspace chats, there may be a secondary cause from another PR in this staging build, most notably PR #82764 (screen freezing changes that could prevent Onyx updates from reaching frozen screens on Android) or PR #83587 (tax rates v2 merge affecting TransactionUtils and IOU/index.ts).
What changes do you think we should make in order to solve the problem?
Immediate fix: Merge the existing revert PR #85157 to revert PR #84658's changes to src/libs/actions/IOU/MoneyRequest.ts. This restores the hardcoded amount: 0 and merchant: 'Pending' values for the skip-confirmation path, matching the pre-regression behavior.
Follow-up: If the original fix from PR #84658 needs to be re-landed (to address the $0.00 amount issue from #82801), the re-implementation should:
Verify that computed amount/merchant values are accepted by the backend TrackExpense and CreateDistanceRequest endpoints before sending them
Add a guard to fall back to amount: 0 and merchant: 'Pending' if the rate calculation returns undefined/NaN
Test specifically with the skip-confirmation (QAB) flow on Android in both 1:1 chats and workspace chats
What alternative solutions did you explore? (Optional)
An alternative would be to keep the amount calculation but only use it for optimistic display, while still sending amount: 0 to the backend (letting the backend recalculate). However, this would only partially fix the original issue (#82801) since the $0.00 display would still appear offline until the backend responds.
Next Steps for Contributor+ team: Reply with @MelvinBot implement this to create a draft PR, @MelvinBot <your feedback> to refine this analysis, or explain why you are rejecting Melvin's proposal.
@MelvinBot the issue can still be seen in the revert PR. Also, the error is happening when creating the expense from within the workspace chat. Any ideas?
Thanks for the feedback — the fact that the revert of #84658 doesn't fix it and the error occurs in workspace chats (where #84658's code doesn't run) completely rules out #84658 as the root cause. Here's my revised analysis:
Please re-state the problem that we are trying to solve in this issue.
Creating a manual distance expense from a workspace chat shows "Unexpected error submitting this expense. Please try again later." after a few seconds. The error persists even with the revert of PR #84658.
What is the root cause of that problem?
PR #82672 ("Confirm page shows Amount field with 0.00 amount when receipt is scanning v2") is the root cause.
This PR added a setMoneyRequestReceiptState function in src/libs/actions/IOU/index.ts that sets receipt: {state: 'OPEN'} on every draft transaction whenever setMoneyRequestAmount, setMoneyRequestCreated, setMoneyRequestDescription, or setMoneyRequestMerchant is called. This contaminates manual distance expense drafts with a spurious receipt object.
The chain of failure:
User enters distance/amount/merchant in the manual distance flow. Each call to setMoneyRequestAmount / setMoneyRequestMerchant triggers setMoneyRequestReceiptState, adding receipt: {state: 'OPEN'} to the draft transaction.
IOURequestStepConfirmation (line 530-535) reads transaction.receipt and stores it in receiptFiles because item.receipt exists ({state: 'OPEN'}) and isLocalFile is false (no source).
At line 976, the createDistanceRequest call passes receipt: receiptFiles[transaction.transactionID] which is {state: 'OPEN'} instead of undefined.
The CreateDistanceRequest API sends this malformed receipt ({state: 'OPEN'} with no source/filename/type) to the backend, which rejects the request. The failureData fires, displaying the "unexpected error" message.
This also explains why the error appears in workspace chats specifically — the confirmation page flow (which is always used for workspace chats) picks up the spurious receipt. The skip-confirmation QAB path (affected by #84658) uses a different code path that doesn't go through IOURequestStepConfirmation's receipt handling.
This same root cause also explains deploy blockers #85152 (split expenses showing "Receipt is missing" error) and #85158 (location modal for manual $0 expenses).
What changes do you think we should make in order to solve the problem?
PR #85256 already reverts PR #82672 and was cherry-picked to staging 9.3.37-4. This should fix the issue. The fix should be verified on staging 9.3.37-4+.
If the original receipt scanning fix from #82672 needs to be re-landed, setMoneyRequestReceiptState should be guarded to only set receipt: {state: 'OPEN'} on transactions that are actual scan requests (e.g., check isScanRequest or transaction.receipt?.source first), rather than all draft transactions unconditionally.
Next Steps for Contributor+ team: Please verify that staging 9.3.37-4+ (with PR #85256 deployed) resolves this issue. If so, this can be closed along with #85152 and #85158.
Activity
melvin-bot commentedon Mar 13, 2026
Triggered auto assignment to @puneetlath (
DeployBlockerCash), see https://stackoverflowteams.com/c/expensify/questions/9980/ for more details.melvin-bot commentedon Mar 13, 2026
💬 A slack conversation has been started in #expensify-open-source
github-actions commentedon Mar 13, 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:
OSBotify commentedon Mar 13, 2026
🔍 Investigation Summary
Classification: Frontend bug
Causing PR: #84658 - "Fix manual distance expense from QAB showing amount as zero" by @MelvinBot (High confidence)
Related Issues: #82801
Recommendation: ROLL FORWARD
The fix in PR #84658 addressed a critical issue (manual distance expenses showing $0.00), but appears to have introduced a regression. Reverting would reintroduce the original zero-amount bug, which is also a deploy blocker. A roll-forward fix is needed to address the unexpected error while preserving the amount calculation logic.
Labels: DeployBlockerCash will be kept (frontend bug blocks App deploy). DeployBlocker will be removed (no backend changes needed).
📋 Detailed Analysis
Evidence
PR #84658 was merged on March 12, 2026 and is included in this staging deploy (v9.3.37-0). The PR modified
src/libs/actions/IOU/MoneyRequest.tsto calculate the amount for manual distance expenses whenshouldSkipConfirmationis true (QAB flow).The new code added:
Verification
src/libs/actions/IOU/MoneyRequest.ts(lines 645-662)Root Cause
The
DistanceRequestUtils.getRate()function or one of its dependencies (getMileageRates,getDefaultMileageRate,getPersonalPolicy) may be throwing an error or returning unexpected values in certain scenarios, such as:customUnitsand distance rates) may not be fully loaded from Onyx yetgetDefaultMileageRatemay returnundefined, causing issues downstreamgetPersonalPolicy()function accessesallPolicieswhich is populated asynchronously viaOnyx.connect, potentially returningundefinedif called too earlyThe original issue #82801 was specifically about the QAB flow with offline creation, but the fix introduced new logic that runs synchronously and may not handle all edge cases gracefully.
Why Roll Forward Instead of Revert
MelvinBot commentedon Mar 13, 2026
Proposal
Please re-state the problem that we are trying to solve in this issue.
Creating a manual distance expense (from a workspace chat or 1:1 chat) shows an "unexpected error" on staging V9.3.37-0 (Android). The error appears a few seconds after creating the expense, when the user opens it. This does not reproduce in production, indicating it is a regression from a PR in the current staging build.
What is the root cause of that problem?
The primary suspect is PR #84658 ("Fix manual distance expense from QAB showing amount as zero"), which modified
src/libs/actions/IOU/MoneyRequest.ts:645-662in thehandleMoneyRequestStepDistanceNavigationfunction's skip-confirmation path.What changed: Previously, the skip-confirmation path hardcoded
amount: 0andmerchant: translate('iou.fieldPending')for manual distance expenses. PR #84658 replaced this with computed values usingDistanceRequestUtils.getRate(),getDistanceRequestAmount(), andgetDistanceMerchant()to calculate a real amount and merchant string from the distance and mileage rate.How the error surfaces: The error message displayed is
iou.error.genericCreateFailureMessage= "Unexpected error submitting this expense. Please try again later." This is set viafailureDatainbuildOnyxDataForMoneyRequest(src/libs/actions/IOU/index.ts:2391-2418), which fires when the backend API (TrackExpenseorCreateDistanceRequest) returns an error response. The error shows as a red brick road (RBR) indicator on the transaction preview and as an error message when opening the transaction thread.Why it fails: The
trackExpenseAPI path (used for TRACK-type expenses in 1:1 chats) now sends a calculatedamountinstead of0. Additionally, themerchantvalue changed from"Pending"to a computed distance merchant string (e.g., "20 mi @ $0.67 / mi"). The backend may be rejecting the request due to the changed parameter values, or there may be an edge case in theDistanceRequestUtils.getRate()call when transaction/policy data isn't fully loaded during the fast skip-confirmation flow.A revert PR (#85157) already exists for PR #84658, confirming it has been identified as problematic.
Note: For the
createDistanceRequestpath (used for SUBMIT-type expenses in workspace chats), theamountis not sent as an API parameter — the backend calculates it independently. If the error also occurs in workspace chats, there may be a secondary cause from another PR in this staging build, most notably PR #82764 (screen freezing changes that could prevent Onyx updates from reaching frozen screens on Android) or PR #83587 (tax rates v2 merge affectingTransactionUtilsandIOU/index.ts).What changes do you think we should make in order to solve the problem?
Immediate fix: Merge the existing revert PR #85157 to revert PR #84658's changes to
src/libs/actions/IOU/MoneyRequest.ts. This restores the hardcodedamount: 0andmerchant: 'Pending'values for the skip-confirmation path, matching the pre-regression behavior.Follow-up: If the original fix from PR #84658 needs to be re-landed (to address the $0.00 amount issue from #82801), the re-implementation should:
TrackExpenseandCreateDistanceRequestendpoints before sending themamount: 0andmerchant: 'Pending'if the rate calculation returns undefined/NaNWhat alternative solutions did you explore? (Optional)
An alternative would be to keep the amount calculation but only use it for optimistic display, while still sending
amount: 0to the backend (letting the backend recalculate). However, this would only partially fix the original issue (#82801) since the $0.00 display would still appear offline until the backend responds.Next Steps for Contributor+ team: Reply with
@MelvinBot implement thisto create a draft PR,@MelvinBot <your feedback>to refine this analysis, or explain why you are rejecting Melvin's proposal.ahmedGaber93 commentedon Mar 13, 2026
@puneetlath I’m still able to reproduce the issue after the revert, so I don’t believe it’s a regression caused by our PR.
20260313070106822.mp4
puneetlath commentedon Mar 13, 2026
@MelvinBot the issue can still be seen in the revert PR. Also, the error is happening when creating the expense from within the workspace chat. Any ideas?
MelvinBot commentedon Mar 13, 2026
Updated Proposal
Thanks for the feedback — the fact that the revert of #84658 doesn't fix it and the error occurs in workspace chats (where #84658's code doesn't run) completely rules out #84658 as the root cause. Here's my revised analysis:
Please re-state the problem that we are trying to solve in this issue.
Creating a manual distance expense from a workspace chat shows "Unexpected error submitting this expense. Please try again later." after a few seconds. The error persists even with the revert of PR #84658.
What is the root cause of that problem?
PR #82672 ("Confirm page shows Amount field with 0.00 amount when receipt is scanning v2") is the root cause.
This PR added a
setMoneyRequestReceiptStatefunction insrc/libs/actions/IOU/index.tsthat setsreceipt: {state: 'OPEN'}on every draft transaction wheneversetMoneyRequestAmount,setMoneyRequestCreated,setMoneyRequestDescription, orsetMoneyRequestMerchantis called. This contaminates manual distance expense drafts with a spurious receipt object.The chain of failure:
User enters distance/amount/merchant in the manual distance flow. Each call to
setMoneyRequestAmount/setMoneyRequestMerchanttriggerssetMoneyRequestReceiptState, addingreceipt: {state: 'OPEN'}to the draft transaction.IOURequestStepConfirmation(line 530-535) readstransaction.receiptand stores it inreceiptFilesbecauseitem.receiptexists ({state: 'OPEN'}) andisLocalFileis false (nosource).At line 976, the
createDistanceRequestcall passesreceipt: receiptFiles[transaction.transactionID]which is{state: 'OPEN'}instead ofundefined.The
CreateDistanceRequestAPI sends this malformed receipt ({state: 'OPEN'}with no source/filename/type) to the backend, which rejects the request. ThefailureDatafires, displaying the "unexpected error" message.This also explains why the error appears in workspace chats specifically — the confirmation page flow (which is always used for workspace chats) picks up the spurious receipt. The skip-confirmation QAB path (affected by #84658) uses a different code path that doesn't go through
IOURequestStepConfirmation's receipt handling.This same root cause also explains deploy blockers #85152 (split expenses showing "Receipt is missing" error) and #85158 (location modal for manual $0 expenses).
What changes do you think we should make in order to solve the problem?
PR #85256 already reverts PR #82672 and was cherry-picked to staging 9.3.37-4. This should fix the issue. The fix should be verified on staging 9.3.37-4+.
If the original receipt scanning fix from #82672 needs to be re-landed,
setMoneyRequestReceiptStateshould be guarded to only setreceipt: {state: 'OPEN'}on transactions that are actual scan requests (e.g., checkisScanRequestortransaction.receipt?.sourcefirst), rather than all draft transactions unconditionally.Next Steps for Contributor+ team: Please verify that staging 9.3.37-4+ (with PR #85256 deployed) resolves this issue. If so, this can be closed along with #85152 and #85158.
m-natarajan commentedon Mar 13, 2026
Passed, Xiaomi Redmi Note 10 / Android 12, App 9.3.37-4
Screenrecorder-2026-03-13-21-12-48-586.mp4
luacmartins commentedon Mar 13, 2026
Fixed. Closing.