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
[Due for payment 2026-04-01] [Due for payment 2026-03-30] Expense - Disabled tax rate appears twice when submitting expense from self DM #85652
Version Number: 9.3.40-0 Reproducible in staging?: Yes Reproducible in production?: No If this was caught during regression testing, add the test name, ID and link from BrowserStack:#78472 Email or phone of affected tester (no customers):sjdoisjndoisjndjnoisj@gmail.com Issue reported by: Applause Internal Team Bug source: Exploratory - Significant User Experience Deterioration Device used: Mac 26.2 / Chrome App Component: Money Requests
👋 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: #78472 - "Improve deleted tax behavior" by @huult (High confidence) Related Issues: #73033 (original issue this PR was fixing)
Recommendation: REVERT
The PR introduced a logic error in the duplicate detection code. While the intent was correct (show deleted tax rates as disabled), the implementation has a bug that causes the deleted tax rate to appear twice.
Labels: Keeping DeployBlockerCash (frontend bug in App). The DeployBlocker label was not present on this issue.
📋 Detailed Analysis
Evidence
PR #78472 was specifically designed to handle deleted tax rates by showing them as disabled options in the tax picker. The issue reports that when submitting an expense from self DM to workspace chat and clicking on Tax, the disabled (deleted) tax rate appears twice - which directly relates to the changes made in this PR.
The PR:
Modified TaxPicker.tsx to add a deletedTaxOption when a tax has been deleted
Changed how tax options are displayed to include the deleted tax as a disabled option
Was merged on 2026-03-18 at 05:37:23Z, shortly before this issue was reported (13:36:52Z)
The issue is on line 131: item.code === deletedTaxOption.code
Looking at line 75-85, deletedTaxOption is created with:
constdeletedTaxOption=!hasTaxBeenDeleted
? null
: {code: undefined,// ← Set to undefined!text: taxValue??,keyForList: taxCode??,// ...};
The deletedTaxOption.code is explicitly set to undefined, so the comparison item.code === deletedTaxOption.code will never match any existing items in the list. This means:
The deleted tax option that may already be in the sections (from getTaxRatesSection) is NOT filtered out
The deletedTaxOption is added to the end of the array
Result: The deleted tax appears twice
The comparison should use keyForList instead of code to properly identify and remove the duplicate before adding the new deleted option.
Please re-state the problem that we are trying to solve in this issue.
When a user creates an expense with a specific tax rate in their self DM, then deletes that tax rate from workspace settings, and subsequently tries to submit the expense to a workspace chat, clicking "Tax" on the confirm page shows the disabled tax rate twice in the picker list.
What is the root cause of that problem?
The duplication is caused by an interaction between two independent code paths in TaxPicker.tsx that both add the deleted tax to the list:
Path 1 — selectedOptions in getTaxRatesSection:
When submitting from self DM, the action is SUBMIT, which causes isMovingTransactionFromTrackExpense() to return true (src/libs/IOUUtils.ts:307-312). This makes getTaxRateTitle() fall through to the isMovingFromTrackExpense branch (src/libs/TransactionUtils/index.ts:2196-2198), which calls getTaxName() with shouldFallbackToValue=true. Since the tax is deleted (not found in policy), this returns transaction.taxValue (e.g., "5%") as a fallback (src/libs/TransactionUtils/index.ts:2155-2156). This non-empty selectedTaxRate creates a selectedOptions entry in getTaxRatesSection, which gets included in the sections as a disabled, selected item with code: undefined (since the selectedOptions object has no code property — src/components/TaxPicker.tsx:87-95).
Path 2 — deletedTaxOption in TaxPicker:
Since the tax code is absent from taxRates.taxes (removed by Onyx after successful deletion), hasTaxBeenDeleted is true (src/components/TaxPicker.tsx:72), and a deletedTaxOption is created with code: undefined and text: "5%" (src/components/TaxPicker.tsx:75-85).
Why the dedup logic fails:
The updatedSections code at line 128-133 tries to deselect the matching item via item.code === deletedTaxOption.code. Since both items have code: undefined, the match succeeds — it sets isSelected: false on the Path 1 item but does not remove it from the list. Then deletedTaxOption is appended as a second entry. The user sees two disabled "5%" entries.
What changes do you think we should make in order to solve the problem?
In src/components/TaxPicker.tsx, when hasTaxBeenDeleted is true, the updatedSections logic should filter out the matching item from the section data (rather than just deselecting it) before appending deletedTaxOption. Change the map at line 131 from:
to a .filter() that removes items where item.code === deletedTaxOption.code, then append deletedTaxOption. This ensures only one entry for the deleted tax appears.
What alternative solutions did you explore?
Clear selectedOptions when hasTaxBeenDeleted is true — Pass empty selectedOptions to getTaxRatesSection when the tax has been deleted. This would prevent Path 1 from adding the entry, but it changes the behavior of section building in a way that could have other side effects.
Fix in getTaxRateTitle — Return empty string when the tax is deleted, even in the isMovingFromTrackExpense path. However, the fallback to taxValue might be intentionally needed for display purposes on the confirm page itself, so removing it could break other UI.
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.
changed the title [-]Expense - Disabled tax rate appears twice when submitting expense from self DM[/-][+][Due for payment 2026-03-30] Expense - Disabled tax rate appears twice when submitting expense from self DM[/+]on Mar 23, 2026
The solution for this issue has been 🚀 deployed to production 🚀 in version 9.3.41-4 and is now subject to a 7-day regression period 📆. Here is the list of pull requests that resolve this issue:
@huult / @thesahindia The PR fixing this issue has been merged! The following checklist (instructions) will need to be completed before the issue can be closed. Please copy/paste the BugZero Checklist from here into a new comment on this GH and complete it. If you have the K2 extension, you can simply click: [this button]
changed the title [-][Due for payment 2026-03-30] Expense - Disabled tax rate appears twice when submitting expense from self DM[/-][+][Due for payment 2026-04-01] [Due for payment 2026-03-30] Expense - Disabled tax rate appears twice when submitting expense from self DM[/+]on Mar 25, 2026
The solution for this issue has been 🚀 deployed to production 🚀 in version 9.3.43-3 and is now subject to a 7-day regression period 📆. Here is the list of pull requests that resolve this issue:
@huult / @thesahindia@mallenexpensify The PR fixing this issue has been merged! The following checklist (instructions) will need to be completed before the issue can be closed. Please copy/paste the BugZero Checklist from here into a new comment on this GH and complete it. If you have the K2 extension, you can simply click: [this button]
Activity
melvin-bot commentedon Mar 18, 2026
You have been assigned to this deploy blocker because you recently merged this PR: #78472
melvin-bot commentedon Mar 18, 2026
💬 A slack conversation has been started in #expensify-open-source
github-actions commentedon Mar 18, 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 18, 2026
🔍 Investigation Summary
Classification: Frontend bug
Causing PR: #78472 - "Improve deleted tax behavior" by @huult (High confidence)
Related Issues: #73033 (original issue this PR was fixing)
Recommendation: REVERT
The PR introduced a logic error in the duplicate detection code. While the intent was correct (show deleted tax rates as disabled), the implementation has a bug that causes the deleted tax rate to appear twice.
Labels: Keeping
DeployBlockerCash(frontend bug in App). TheDeployBlockerlabel was not present on this issue.📋 Detailed Analysis
Evidence
PR #78472 was specifically designed to handle deleted tax rates by showing them as disabled options in the tax picker. The issue reports that when submitting an expense from self DM to workspace chat and clicking on Tax, the disabled (deleted) tax rate appears twice - which directly relates to the changes made in this PR.
The PR:
TaxPicker.tsxto add adeletedTaxOptionwhen a tax has been deletedVerification
The affected file is
/home/runner/_work/App/App/src/components/TaxPicker.tsxConfirmed that PR #78472 modified this exact file and introduced the problematic code that is currently present in the codebase.
Root Cause
The bug is in lines 128-133 of
TaxPicker.tsx:The issue is on line 131:
item.code === deletedTaxOption.codeLooking at line 75-85,
deletedTaxOptionis created with:The
deletedTaxOption.codeis explicitly set toundefined, so the comparisonitem.code === deletedTaxOption.codewill never match any existing items in the list. This means:getTaxRatesSection) is NOT filtered outdeletedTaxOptionis added to the end of the arrayThe comparison should use
keyForListinstead ofcodeto properly identify and remove the duplicate before adding the new deleted option.MelvinBot commentedon Mar 18, 2026
Proposal
Please re-state the problem that we are trying to solve in this issue.
When a user creates an expense with a specific tax rate in their self DM, then deletes that tax rate from workspace settings, and subsequently tries to submit the expense to a workspace chat, clicking "Tax" on the confirm page shows the disabled tax rate twice in the picker list.
What is the root cause of that problem?
The duplication is caused by an interaction between two independent code paths in
TaxPicker.tsxthat both add the deleted tax to the list:Path 1 —
selectedOptionsingetTaxRatesSection:When submitting from self DM, the action is
SUBMIT, which causesisMovingTransactionFromTrackExpense()to returntrue(src/libs/IOUUtils.ts:307-312). This makesgetTaxRateTitle()fall through to theisMovingFromTrackExpensebranch (src/libs/TransactionUtils/index.ts:2196-2198), which callsgetTaxName()withshouldFallbackToValue=true. Since the tax is deleted (not found in policy), this returnstransaction.taxValue(e.g.,"5%") as a fallback (src/libs/TransactionUtils/index.ts:2155-2156). This non-emptyselectedTaxRatecreates aselectedOptionsentry ingetTaxRatesSection, which gets included in the sections as a disabled, selected item withcode: undefined(since the selectedOptions object has nocodeproperty —src/components/TaxPicker.tsx:87-95).Path 2 —
deletedTaxOptioninTaxPicker:Since the tax code is absent from
taxRates.taxes(removed by Onyx after successful deletion),hasTaxBeenDeletedistrue(src/components/TaxPicker.tsx:72), and adeletedTaxOptionis created withcode: undefinedandtext: "5%"(src/components/TaxPicker.tsx:75-85).Why the dedup logic fails:
The
updatedSectionscode at line 128-133 tries to deselect the matching item viaitem.code === deletedTaxOption.code. Since both items havecode: undefined, the match succeeds — it setsisSelected: falseon the Path 1 item but does not remove it from the list. ThendeletedTaxOptionis appended as a second entry. The user sees two disabled "5%" entries.What changes do you think we should make in order to solve the problem?
In
src/components/TaxPicker.tsx, whenhasTaxBeenDeletedis true, theupdatedSectionslogic should filter out the matching item from the section data (rather than just deselecting it) before appendingdeletedTaxOption. Change the map at line 131 from:to a
.filter()that removes items whereitem.code === deletedTaxOption.code, then appenddeletedTaxOption. This ensures only one entry for the deleted tax appears.What alternative solutions did you explore?
selectedOptionswhenhasTaxBeenDeletedis true — Pass emptyselectedOptionstogetTaxRatesSectionwhen the tax has been deleted. This would prevent Path 1 from adding the entry, but it changes the behavior of section building in a way that could have other side effects.getTaxRateTitle— Return empty string when the tax is deleted, even in theisMovingFromTrackExpensepath. However, the fallback totaxValuemight be intentionally needed for display purposes on the confirm page itself, so removing it could break other UI.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.huult commentedon Mar 18, 2026
checking...
9 remaining items
[-]Expense - Disabled tax rate appears twice when submitting expense from self DM[/-][+][Due for payment 2026-03-30] Expense - Disabled tax rate appears twice when submitting expense from self DM[/+]melvin-bot commentedon Mar 23, 2026
Reviewinglabel has been removed, please complete the "BugZero Checklist".melvin-bot commentedon Mar 23, 2026
The solution for this issue has been 🚀 deployed to production 🚀 in version 9.3.41-4 and is now subject to a 7-day regression period 📆. Here is the list of pull requests that resolve this issue:
If no regressions arise, payment will be issued on 2026-03-30. 🎊
For reference, here are some details about the assignees on this issue:
melvin-bot commentedon Mar 23, 2026
@huult / @thesahindia The PR fixing this issue has been merged! The following checklist (instructions) will need to be completed before the issue can be closed. Please copy/paste the BugZero Checklist from here into a new comment on this GH and complete it. If you have the K2 extension, you can simply click: [this button]
melvin-bot commentedon Mar 23, 2026
Triggered auto assignment to @mallenexpensify (
Awaiting Payment)[-][Due for payment 2026-03-30] Expense - Disabled tax rate appears twice when submitting expense from self DM[/-][+][Due for payment 2026-04-01] [Due for payment 2026-03-30] Expense - Disabled tax rate appears twice when submitting expense from self DM[/+]melvin-bot commentedon Mar 25, 2026
The solution for this issue has been 🚀 deployed to production 🚀 in version 9.3.43-3 and is now subject to a 7-day regression period 📆. Here is the list of pull requests that resolve this issue:
If no regressions arise, payment will be issued on 2026-04-01. 🎊
For reference, here are some details about the assignees on this issue:
melvin-bot commentedon Mar 25, 2026
@huult / @thesahindia @mallenexpensify The PR fixing this issue has been merged! The following checklist (instructions) will need to be completed before the issue can be closed. Please copy/paste the BugZero Checklist from here into a new comment on this GH and complete it. If you have the K2 extension, you can simply click: [this button]