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
[$175] Tags - Not here page opens after editing custom tag name from uploaded CSV #105001
Version Number: 9.5.20-0 Reproducible in staging?: Yes Reproducible in production?: N/A - new feature, doesn't exist in prod If this was caught during regression testing, add the test name, ID and link from BrowserStack:#103359 Email or phone of affected tester (no customers):[email protected] Issue reported by: Applause Internal Team Bug source: Exploratory - Significant User Experience Deterioration Device used: Mac 26.5 / Chrome App Component: Workspace Settings
Action Performed:
Precondition:
Use expensifail account and enable indirectTagUploads beta
Connect to NetSuite.
Go to staging.new.expensify.com
Go to workspace settings > Accounting.
Click Import > Departments > Tag.
Go to Tags.
Click More > Append custom tag list.
Upload multi level tag CSV.
Open Department tag from the uploaded CSV.
Click Custom tag name.
Edit name and save it.
Expected Result:
Name will be saved without issue.
Actual Result:
Not here page opens after editing custom tag name from uploaded CSV.
👋 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.
Recommendation: revert 103359. The PR is self-contained and behind the indirectTagUploads beta, so the revert should be clean. A fix forward isn't practical yet. The tag-list data that breaks the page comes from the server, so the App fix depends on what the server returns.
Why "Not here" shows: Every page in this flow finds the tag list by the orderWeight number in the URL, not by name. After the rename, no tag list has that orderWeight, so the list page shows NotFoundPage.
Investigation details
All links are at 21045e266fdb050a4761b043e0350ef903f8888b.
The routes are workspace-tag-list/:orderWeight and workspace-edit-tags/:orderWeight: src/ROUTES.ts:1271-1288.
The optimistic update in renamePolicyTagList keeps orderWeight when it moves the list to the new key (src/libs/actions/Policy/Tag.ts:984-994). It sends tagListIndex: orderWeight to RenamePolicyTaglist (Tag.ts:1022-1029). The local update alone can't cause the bug.
103359 doesn't touch the rename code. It adds no optimistic tag lists. It only passes appendToExistingLists to ImportMultiLevelTags, so the server assigns the appended lists' orderWeight values.
Likely mechanism (unconfirmed): The appended lists' orderWeight values clash with the NetSuite list or don't match the server's list positions. The RenamePolicyTaglist response then returns the renamed list with a different orderWeight. The CSV's first column is "Department", the same name as the NetSuite import, which makes a clash plausible.
Risk: If two lists share an orderWeight, getTagListName returns the first match. The rename could then send the wrong tagListIndex and rename the NetSuite list.
Other PRs ruled out: I checked the file lists of all PRs in the checklist. Skip already escaped colons when escaping tag names only changes escapeTagName, which the rename doesn't use. No other PR touches tags or these routes.
Not tested in the UI: The repro needs an expensifail account with NetSuite connected and the beta enabled.
Before deciding, you can check:
Compare the policyTags_<policyID> data (key, name, orderWeight) after the append and after the rename. Also check the RenamePolicyTaglist response.
On staging, refresh after the rename and check which list was renamed. If only navigation breaks and the right list is renamed, you can demote instead of reverting. Don't demote if the NetSuite list gets renamed.
Assignees are already correct. I skipped the code review rule because the mechanism depends on server data.
I don't think this is a regression from #103359, that PR doesn't update DynamicWorkspaceEditTagsPage or DynamicWorkspaceViewTagsPage at all, so I think it's a flaky bug.
CleanShot.2026-10-26.at.21.24.16.1.mp4
@mitarachim Could you please retest and confirm whether it consistently reproduces, and whether the "Not here" page recovers on its own after a refresh? Thanks!
⚠️Preliminary analysis (low confidence) — treat as a starting point, not a conclusion.
This doesn't look flaky. The original tester reproduces it every time they rename the first list from the CSV ("Department"). The retest skipped that step.
I think the backend returns the renamed list with a different orderWeight than the one in the URL, or stores it under a key that doesn't match its name. The view page finds the list by orderWeight and shows "Not here" when nothing matches: DynamicWorkspaceViewTagsPage.tsx:84-87 and :185-187. The App's own rename keeps orderWeight unchanged (Tag.ts:986-994), so the App can't create this state alone.
Likely trigger: the CSV's first column ("Department") gets the same orderWeight as the existing NetSuite departments list. That would explain why only the first CSV list breaks.
I couldn't verify this. I had no NetSuite-connected test account, and I couldn't read backend code or logs in this run.
To confirm, an internal engineer should check the tester's policy:
policyTags_<policyID> after the append: each list's key, name, and orderWeight. Does "Department" share an orderWeight with the NetSuite list?
The RenamePolicyTaglist request (oldName, tagListIndex) and the Onyx data it returns.
Fix direction:
If confirmed, the real fix is in the backend. Appended lists need unique orderWeights after the existing lists, and rename must keep orderWeight. That is outside the App repo.
App hardening only (it won't fix bad data):
In the view page, use the list object found by orderWeight directly instead of policyTags[name]. This covers a key that doesn't match name.
changed the title [-]Tags - Not here page opens after editing custom tag name from uploaded CSV[/-][+][$175] Tags - Not here page opens after editing custom tag name from uploaded CSV[/+]on Oct 27, 2026
Please re-state the problem that we are trying to solve in this issue.
When a workspace is connected to an accounting integration (such as NetSuite with Import > Departments > Import as Tag) and the admin uses More > Append custom tag list (indirectTagUploads beta) to upload an independent multi-level CSV (Department,Region,Third,Four), opening the first appended custom tag list (Department) and renaming its Custom tag name causes DynamicWorkspaceViewTagsPage to show the <NotFoundPage /> ("Hmm... it's not here") screen.
What is the root cause of that problem?
There are two interacting issues in how appended multi-level tag lists are imported, looked up by orderWeight, and renamed:
Duplicate orderWeight collision and lookup mismatch in DynamicWorkspaceViewTagsPage.tsx and src/libs/PolicyUtils/tag.ts:
When ImportMultiLevelTags runs with appendToExistingLists: true (src/libs/actions/Policy/Tag.ts:955-962), the workspace keeps its existing accounting tag list (Departments at orderWeight: 0) alongside the newly uploaded CSV lists (Department, Region, Third, Four).
In src/pages/workspace/tags/DynamicWorkspaceViewTagsPage.tsx (lines 84–87):
First, unlike getTagLists (line 44, which filters .filter((policyTagListValue) => policyTagListValue !== null)), getTagListName and getTagListByOrderWeight do not guard against null/falsy entries or key/name mismatches in policyTagList.
Second, when renamePolicyTagList (src/libs/actions/Policy/Tag.ts:981-1030) executes:
Deleting [oldName] (null) and inserting [newName] moves the renamed tag list entry to the end of the policyTags object key order, and when the RenamePolicyTagList backend command responds (or when another list shares the same orderWeight: 0 ahead of [newName] in Object.values(policyTags) / when the backend normalizes the appended list's orderWeight), getTagListName(policyTags, orderWeight) either resolves to the earlier list sharing orderWeight or returns '' when the orderWeight shifts, causing currentPolicyTag at DynamicWorkspaceViewTagsPage.tsx:185 (if (!currentPolicyTag) return <NotFoundPage />;) to evaluate to undefined and render <NotFoundPage />.
Skipping ImportMultiLevelTagsSettingsPage on append never initializes isImportingIndependentMultiLevelTags (src/pages/workspace/tags/WorkspaceTagsPage.tsx:448-452):
In WorkspaceTagsPage.tsx (lines 449–450), navigateToAppendCustomTagList calls setImportedSpreadsheetIsImportingMultiLevelTags(true) and setImportedSpreadsheetIsAppendingToExistingLists(true), and in ImportMultiLevelTagsSettingsPage.tsx (line 163) the independent-tags toggle is hidden when isAppendingToExistingLists is true (/* Appended tag lists are always independent, so the dependent option isn't offered */). Ensure isIndependent: spreadsheet.isAppendingToExistingLists ? true : spreadsheet.isImportingIndependentMultiLevelTags is always true in importMultiLevelTags (src/libs/actions/Policy/Tag.ts:958).
What changes do you think we should make in order to solve the problem?
In src/libs/PolicyUtils/tag.ts (getTagListName and getTagListByOrderWeight):
Filter out nullish entries (Boolean) before calling .find((tag) => tag.orderWeight === orderWeight).
In src/pages/workspace/tags/DynamicWorkspaceViewTagsPage.tsx and src/pages/workspace/tags/DynamicWorkspaceEditTagsPage.tsx:
Look up currentPolicyTag directly via getTagListByOrderWeight(policyTags, orderWeight) (or preserve the active tag list across renames when multiple lists exist / when orderWeight is updated by the server response) instead of doing a two-step getTagListName -> policyTags?.[currentTagListName] lookup, and if the renamed tag list's orderWeight changes on server sync while viewing DynamicWorkspaceViewTagsPage, keep resolving the renamed list (or update the route orderWeight param) so currentPolicyTag does not become undefined.
In src/libs/actions/Policy/Tag.ts (renamePolicyTagList & importMultiLevelTags):
In renamePolicyTagList (line 991), ensure orderWeight: tagListIndex is explicitly preserved on [newName] in optimisticData, and in importMultiLevelTags (line 958), pass isIndependent: spreadsheet.isAppendingToExistingLists ? true : spreadsheet.isImportingIndependentMultiLevelTags.
What alternative solutions did you explore? (Optional)
Passing the tag list name in the route params of DYNAMIC_TAG_LIST_VIEW in addition to orderWeight was considered, but keeping orderWeight as the route param while making renamePolicyTagList and DynamicWorkspaceViewTagsPage resilient to key reordering and orderWeight collisions avoids changing all existing dynamic route callers.
Please re-state the problem that we are trying to solve in this issue.
When a workspace is connected to NetSuite with Import > Departments > Tag enabled and the admin uses More > Append custom tag list (indirectTagUploads beta) to upload Independent.-.Multi.Level.tags.csv (Department,Region,Third,Four), opening the first uploaded custom tag list (Department) and renaming its Custom tag name causes DynamicWorkspaceViewTagsPage to open <NotFoundPage /> ("Hmm... it's not here").
What is the root cause of that problem?
This reproduces specifically when the uploaded CSV's first column (Department in Independent.-.Multi.Level.tags.csv) shares its name/key with the existing NetSuite Department tag list (which is why uploading a different CSV without a colliding Department column did not reproduce the issue in earlier retests):
Two-step name-key lookup in DynamicWorkspaceViewTagsPage.tsx (lines 84-87):
Instead of resolving currentPolicyTag directly by orderWeight, DynamicWorkspaceViewTagsPage resolves currentTagListName via getTagListName(policyTags, orderWeight) (src/libs/PolicyUtils/tag.ts:27-33) and then indexes policyTags?.[currentTagListName]. Whenever the Onyx dictionary key in policyTags differs from tag.name (or when two tag lists collide on the name "Department" / orderWeight), policyTags?.[currentTagListName] either points to the wrong list or evaluates to undefined, hitting if (!currentPolicyTag) { return <NotFoundPage />; } at DynamicWorkspaceViewTagsPage.tsx:185-187.
Key-based lookup instead of orderWeight lookup in renamePolicyTagList (src/libs/actions/Policy/Tag.ts:981-1020):
renamePolicyTagList looks up oldPolicyTags via policyTags?.[oldName] instead of locating the entry whose orderWeight === tagListIndex.
When the appended CSV list (Department) collides with the existing NetSuite Department list (or is stored under a disambiguated key / shares orderWeight), policyTags?.[oldName] either resolves to the NetSuite list (copying its orderWeight instead of tagListIndex) or falls back to {} (creating [newName] with orderWeight: undefined), while setting [oldName]: null deletes the "Department" key.
Furthermore, when [oldName]: null and [newName]: {...} are merged into policyTags, [newName] is appended to the end of Object.values(policyTags). If another list shares the same orderWeight (or if the backend RenamePolicyTaglist response normalizes the appended list's orderWeight), getTagListName(policyTags, orderWeight) no longer resolves to the renamed list, causing DynamicWorkspaceViewTagsPage to immediately render <NotFoundPage />.
Missing null-guards in getTagListName, getTagListByOrderWeight, and getSortedTagKeys (src/libs/PolicyUtils/tag.ts:16-33, 173-185):
Unlike getTagLists (line 44, which filters .filter((policyTagListValue) => policyTagListValue !== null)), getTagListName and getTagListByOrderWeight call Object.values(policyTagList).find((tag) => tag.orderWeight === orderWeight) without guarding against null entries left during Onyx merges.
What changes do you think we should make in order to solve the problem?
In src/libs/PolicyUtils/tag.ts (lines 16-33, 173-185):
Filter out nullish entries (Boolean(tag)) in getSortedTagKeys, getTagListName, and getTagListByOrderWeight, and add a helper findTagListEntryByOrderWeight(policyTagList: OnyxEntry<PolicyTagLists>, orderWeight: number) that returns [tagKey, tagList] for the matching orderWeight:
functionfindTagListEntryByOrderWeight(policyTagList: OnyxEntry<PolicyTagLists>,orderWeight: number): [string,ValueOf<PolicyTagLists>]|undefined{if(isEmptyObject(policyTagList)){returnundefined;}returnObject.entries(policyTagList).find((entry): entry is [string,ValueOf<PolicyTagLists>]=>Boolean(entry[1])&&entry[1].orderWeight===orderWeight);}
In src/pages/workspace/tags/DynamicWorkspaceViewTagsPage.tsx (lines 84-87):
Resolve currentPolicyTag directly from the entry matching orderWeight (with a fallback to the last resolved renamed tag list key while focused if the server normalizes orderWeight on rename) instead of doing policyTags?.[currentTagListName]:
In src/libs/actions/Policy/Tag.ts (renamePolicyTagList, lines 981-1020):
Resolve [oldKey, oldPolicyTags] using findTagListEntryByOrderWeight(policyTags, tagListIndex) (falling back to [oldName, policyTags?.[oldName]]), and explicitly set orderWeight: tagListIndex on [newName] in optimisticData:
In src/pages/workspace/tags/WorkspaceTagsPage.tsx (line 328):
Use ${policyTagList.name}_${policyTagList.orderWeight} for keyForList in multi-level mode so two tag lists with the same display name (Department) never collide on list item keys.
What alternative solutions did you explore? (Optional)
Navigating to tag lists by name instead of orderWeight: rejected because dynamic tag routes (DYNAMIC_TAG_LIST_VIEW / DYNAMIC_TAGS_EDIT) are keyed by :orderWeight across the entire app, and two lists can temporarily share the same display name (Department) right after appending a CSV. Resolving by orderWeight directly and preserving orderWeight: tagListIndex in renamePolicyTagList fixes the bug cleanly without changing route schemas.
I looked into what happens after the import and after the rename.
1. Data after appending the CSV
The CSV's first column is named like the NetSuite list (Department). After the append, policyTags_<policyID> has two lists with the same name. The BE stores the new one under a different key:
Onyx key
name
orderWeight
Tags
Department
Department
1
16 (NetSuite, externalID: "Departments")
Department_2
Department
2
6 (from the CSV)
2. Why the rename ends on "Not here" (code at the time of the report)
Opening the CSV list goes to /workspace-tag-list/2. The page converts orderWeight 2 to a name and looks the list up by that name, so it gets the NetSuite list (key Department) and shows its 16 tags: DynamicWorkspaceViewTagsPage.tsx#L84-L87, getTagListName
The request returns 200, and the BE renames the NetSuite list (externalID: "Departments"). The CSV list is still named Department.
Back on /workspace-tag-list/2, the name for orderWeight 2 is still Department, but the key Department no longer exists, so currentPolicyTag is undefined and the page shows Not Found: DynamicWorkspaceViewTagsPage.tsx#L185
So the request succeeds, but it renames the wrong list, and the NetSuite list's name gets changed.
3. Current main
#105212 (merged for #105010) now finds tag lists by orderWeight and Onyx key on the App side: DynamicWorkspaceViewTagsPage.tsx#L84, renamePolicyTagList. With an equivalent local patch, the App opened the right list and sent oldName: "Dept CSV", tagListIndex: 2, but the BE still renamed the NetSuite list at index 1, then the page showed Not Found again. So RenamePolicyTaglist finds the list by oldName and ignores tagListIndex, which #105212 also notes.
@yuwenmemon I suggest fixing this in the BE append: it shouldn't create a list whose name already exists. Either reject the import with an error, or rename the new list (its name too, not only the key). That matches the rule Classic's rename already has, stops duplicate lists from being created on both platforms, and keeps every name-based lookup in NewDot (tag pages, tag picker on expenses) unambiguous. What do you think?
Well, technically, we do allow two tag lists with the same name. In fact, we added support for this here https://github.com/Expensify/Auth/pull/20714 about 7 months ago (stemming from https://github.com/Expensify/Expensify/issues/615983#issuecomment-4127986841). So blocking this wouldn't necessarily line up with how the feature is designed. We could start paying attention to the tagListIndex App sends to account for this... we're kind of ignoring it for the sake of the rename for now.
Activity
applause-bot commentedon Oct 26, 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/a309ffe7e4a8685f026d85cfaff9af472076771126f49d86acd331c7a953d8c0
https://platform.applause.com/services/links/v1/external/7dba93bc484a05677f04546a5dbd8cebaa420ac639422ca6373cde4e45d036a7
melvin-bot commentedon Oct 26, 2026
You have been assigned to this deploy blocker because you recently merged this PR: #103359
melvin-bot commentedon Oct 26, 2026
@yuwenmemon @suneox you have been assigned to this deploy blocker because you reviewed the offending PR: #103359
melvin-bot commentedon Oct 26, 2026
💬 A slack conversation has been started in #expensify-open-source
github-actions commentedon Oct 26, 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:
MelvinBot commentedon Oct 26, 2026
Deploy Blocker Investigation
Causing PR (medium confidence): Add Append custom tag list for accounting-connected workspaces on the indirectTagUploads beta. It is the only PR in Deploy Checklist: New Expensify 2026-10-23 that makes this flow reachable. Before it, connected workspaces could not add a custom tag list next to their NetSuite lists.
Recommendation: revert 103359. The PR is self-contained and behind the
indirectTagUploadsbeta, so the revert should be clean. A fix forward isn't practical yet. The tag-list data that breaks the page comes from the server, so the App fix depends on what the server returns.Why "Not here" shows: Every page in this flow finds the tag list by the
orderWeightnumber in the URL, not by name. After the rename, no tag list has thatorderWeight, so the list page showsNotFoundPage.Investigation details
All links are at
21045e266fdb050a4761b043e0350ef903f8888b.workspace-tag-list/:orderWeightandworkspace-edit-tags/:orderWeight: src/ROUTES.ts:1271-1288.getTagListName(policyTags, orderWeight): DynamicWorkspaceViewTagsPage.tsx:65-66. That needs an exacttag.orderWeight === orderWeightmatch (src/libs/PolicyUtils/tag.ts:27-33). If nothing matches, it rendersNotFoundPage(DynamicWorkspaceViewTagsPage.tsx:202-204).workspace-tag-list/<orderWeight>: DynamicWorkspaceEditTagsPage.tsx:61-70.renamePolicyTagListkeepsorderWeightwhen it moves the list to the new key (src/libs/actions/Policy/Tag.ts:984-994). It sendstagListIndex: orderWeighttoRenamePolicyTaglist(Tag.ts:1022-1029). The local update alone can't cause the bug.appendToExistingListstoImportMultiLevelTags, so the server assigns the appended lists'orderWeightvalues.Likely mechanism (unconfirmed): The appended lists'
orderWeightvalues clash with the NetSuite list or don't match the server's list positions. TheRenamePolicyTaglistresponse then returns the renamed list with a differentorderWeight. The CSV's first column is "Department", the same name as the NetSuite import, which makes a clash plausible.Risk: If two lists share an
orderWeight,getTagListNamereturns the first match. The rename could then send the wrongtagListIndexand rename the NetSuite list.Other PRs ruled out: I checked the file lists of all PRs in the checklist. Skip already escaped colons when escaping tag names only changes
escapeTagName, which the rename doesn't use. No other PR touches tags or these routes.Not tested in the UI: The repro needs an expensifail account with NetSuite connected and the beta enabled.
Before deciding, you can check:
policyTags_<policyID>data (key,name,orderWeight) after the append and after the rename. Also check theRenamePolicyTaglistresponse.Assignees are already correct. I skipped the code review rule because the mechanism depends on server data.
view run
suneox commentedon Oct 26, 2026
I can not reproduce this issue on latest main & staging
9.5.20-0CleanShot.2026-10-26.at.20.43.52.1.mp4
suneox commentedon Oct 26, 2026
I don't think this is a regression from #103359, that PR doesn't update
DynamicWorkspaceEditTagsPageorDynamicWorkspaceViewTagsPageat all, so I think it's a flaky bug.CleanShot.2026-10-26.at.21.24.16.1.mp4
@mitarachim Could you please retest and confirm whether it consistently reproduces, and whether the "Not here" page recovers on its own after a refresh? Thanks!
8 remaining items
suneox commentedon Oct 27, 2026
@MelvinBot Analyze the flaky issue and provide a fix
MelvinBot commentedon Oct 27, 2026
This doesn't look flaky. The original tester reproduces it every time they rename the first list from the CSV ("Department"). The retest skipped that step.
I think the backend returns the renamed list with a different
orderWeightthan the one in the URL, or stores it under a key that doesn't match itsname. The view page finds the list byorderWeightand shows "Not here" when nothing matches: DynamicWorkspaceViewTagsPage.tsx:84-87 and :185-187. The App's own rename keepsorderWeightunchanged (Tag.ts:986-994), so the App can't create this state alone.Likely trigger: the CSV's first column ("Department") gets the same
orderWeightas the existing NetSuite departments list. That would explain why only the first CSV list breaks.I couldn't verify this. I had no NetSuite-connected test account, and I couldn't read backend code or logs in this run.
To confirm, an internal engineer should check the tester's policy:
policyTags_<policyID>after the append: each list's key,name, andorderWeight. Does "Department" share anorderWeightwith the NetSuite list?RenamePolicyTaglistrequest (oldName,tagListIndex) and the Onyx data it returns.Fix direction:
orderWeights after the existing lists, and rename must keeporderWeight. That is outside the App repo.orderWeightdirectly instead ofpolicyTags[name]. This covers a key that doesn't matchname.orderWeight(DynamicWorkspaceEditTagsPage.tsx:55).Next Steps: Reply with
@MelvinBot <your feedback>to refine this analysis.view run
melvin-bot commentedon Oct 27, 2026
Current assignee @suneox is eligible for the External assigner, not assigning anyone new.
[-]Tags - Not here page opens after editing custom tag name from uploaded CSV[/-][+][$175] Tags - Not here page opens after editing custom tag name from uploaded CSV[/+]melvin-bot commentedon Oct 27, 2026
Job added to Upwork: https://www.upwork.com/jobs/~022115082329933289178
Kabeer099 commentedon Oct 27, 2026
Proposal
Please re-state the problem that we are trying to solve in this issue.
When a workspace is connected to an accounting integration (such as NetSuite with
Import > Departments > Import as Tag) and the admin uses More > Append custom tag list (indirectTagUploadsbeta) to upload an independent multi-level CSV (Department,Region,Third,Four), opening the first appended custom tag list (Department) and renaming its Custom tag name causesDynamicWorkspaceViewTagsPageto show the<NotFoundPage />("Hmm... it's not here") screen.What is the root cause of that problem?
There are two interacting issues in how appended multi-level tag lists are imported, looked up by
orderWeight, and renamed:Duplicate
orderWeightcollision and lookup mismatch inDynamicWorkspaceViewTagsPage.tsxandsrc/libs/PolicyUtils/tag.ts:ImportMultiLevelTagsruns withappendToExistingLists: true(src/libs/actions/Policy/Tag.ts:955-962), the workspace keeps its existing accounting tag list (DepartmentsatorderWeight: 0) alongside the newly uploaded CSV lists (Department,Region,Third,Four).src/pages/workspace/tags/DynamicWorkspaceViewTagsPage.tsx(lines 84–87):src/libs/PolicyUtils/tag.ts(lines 27–33):getTagLists(line 44, which filters.filter((policyTagListValue) => policyTagListValue !== null)),getTagListNameandgetTagListByOrderWeightdo not guard againstnull/falsy entries or key/name mismatches inpolicyTagList.renamePolicyTagList(src/libs/actions/Policy/Tag.ts:981-1030) executes:[oldName](null) and inserting[newName]moves the renamed tag list entry to the end of thepolicyTagsobject key order, and when theRenamePolicyTagListbackend command responds (or when another list shares the sameorderWeight: 0ahead of[newName]inObject.values(policyTags)/ when the backend normalizes the appended list'sorderWeight),getTagListName(policyTags, orderWeight)either resolves to the earlier list sharingorderWeightor returns''when theorderWeightshifts, causingcurrentPolicyTagatDynamicWorkspaceViewTagsPage.tsx:185(if (!currentPolicyTag) return <NotFoundPage />;) to evaluate toundefinedand render<NotFoundPage />.Skipping
ImportMultiLevelTagsSettingsPageon append never initializesisImportingIndependentMultiLevelTags(src/pages/workspace/tags/WorkspaceTagsPage.tsx:448-452):WorkspaceTagsPage.tsx(lines 449–450),navigateToAppendCustomTagListcallssetImportedSpreadsheetIsImportingMultiLevelTags(true)andsetImportedSpreadsheetIsAppendingToExistingLists(true), and inImportMultiLevelTagsSettingsPage.tsx(line 163) the independent-tags toggle is hidden whenisAppendingToExistingListsistrue(/* Appended tag lists are always independent, so the dependent option isn't offered */). EnsureisIndependent: spreadsheet.isAppendingToExistingLists ? true : spreadsheet.isImportingIndependentMultiLevelTagsis alwaystrueinimportMultiLevelTags(src/libs/actions/Policy/Tag.ts:958).What changes do you think we should make in order to solve the problem?
src/libs/PolicyUtils/tag.ts(getTagListNameandgetTagListByOrderWeight):Boolean) before calling.find((tag) => tag.orderWeight === orderWeight).src/pages/workspace/tags/DynamicWorkspaceViewTagsPage.tsxandsrc/pages/workspace/tags/DynamicWorkspaceEditTagsPage.tsx:currentPolicyTagdirectly viagetTagListByOrderWeight(policyTags, orderWeight)(or preserve the active tag list across renames when multiple lists exist / whenorderWeightis updated by the server response) instead of doing a two-stepgetTagListName->policyTags?.[currentTagListName]lookup, and if the renamed tag list'sorderWeightchanges on server sync while viewingDynamicWorkspaceViewTagsPage, keep resolving the renamed list (or update the routeorderWeightparam) socurrentPolicyTagdoes not becomeundefined.src/libs/actions/Policy/Tag.ts(renamePolicyTagList&importMultiLevelTags):renamePolicyTagList(line 991), ensureorderWeight: tagListIndexis explicitly preserved on[newName]inoptimisticData, and inimportMultiLevelTags(line 958), passisIndependent: spreadsheet.isAppendingToExistingLists ? true : spreadsheet.isImportingIndependentMultiLevelTags.What alternative solutions did you explore? (Optional)
Passing the tag list
namein the route params ofDYNAMIC_TAG_LIST_VIEWin addition toorderWeightwas considered, but keepingorderWeightas the route param while makingrenamePolicyTagListandDynamicWorkspaceViewTagsPageresilient to key reordering andorderWeightcollisions avoids changing all existing dynamic route callers.Enes2553 commentedon Oct 28, 2026
Proposal
Please re-state the problem that we are trying to solve in this issue.
When a workspace is connected to NetSuite with Import > Departments > Tag enabled and the admin uses More > Append custom tag list (
indirectTagUploadsbeta) to uploadIndependent.-.Multi.Level.tags.csv(Department,Region,Third,Four), opening the first uploaded custom tag list (Department) and renaming its Custom tag name causesDynamicWorkspaceViewTagsPageto open<NotFoundPage />("Hmm... it's not here").What is the root cause of that problem?
This reproduces specifically when the uploaded CSV's first column (
DepartmentinIndependent.-.Multi.Level.tags.csv) shares its name/key with the existing NetSuiteDepartmenttag list (which is why uploading a different CSV without a collidingDepartmentcolumn did not reproduce the issue in earlier retests):Two-step name-key lookup in
DynamicWorkspaceViewTagsPage.tsx(lines 84-87):Instead of resolving
currentPolicyTagdirectly byorderWeight,DynamicWorkspaceViewTagsPageresolvescurrentTagListNameviagetTagListName(policyTags, orderWeight)(src/libs/PolicyUtils/tag.ts:27-33) and then indexespolicyTags?.[currentTagListName]. Whenever the Onyx dictionary key inpolicyTagsdiffers fromtag.name(or when two tag lists collide on the name"Department"/orderWeight),policyTags?.[currentTagListName]either points to the wrong list or evaluates toundefined, hittingif (!currentPolicyTag) { return <NotFoundPage />; }atDynamicWorkspaceViewTagsPage.tsx:185-187.Key-based lookup instead of
orderWeightlookup inrenamePolicyTagList(src/libs/actions/Policy/Tag.ts:981-1020):renamePolicyTagListlooks upoldPolicyTagsviapolicyTags?.[oldName]instead of locating the entry whoseorderWeight === tagListIndex.Department) collides with the existing NetSuiteDepartmentlist (or is stored under a disambiguated key / sharesorderWeight),policyTags?.[oldName]either resolves to the NetSuite list (copying itsorderWeightinstead oftagListIndex) or falls back to{}(creating[newName]withorderWeight: undefined), while setting[oldName]: nulldeletes the"Department"key.[oldName]: nulland[newName]: {...}are merged intopolicyTags,[newName]is appended to the end ofObject.values(policyTags). If another list shares the sameorderWeight(or if the backendRenamePolicyTaglistresponse normalizes the appended list'sorderWeight),getTagListName(policyTags, orderWeight)no longer resolves to the renamed list, causingDynamicWorkspaceViewTagsPageto immediately render<NotFoundPage />.Missing null-guards in
getTagListName,getTagListByOrderWeight, andgetSortedTagKeys(src/libs/PolicyUtils/tag.ts:16-33, 173-185):Unlike
getTagLists(line 44, which filters.filter((policyTagListValue) => policyTagListValue !== null)),getTagListNameandgetTagListByOrderWeightcallObject.values(policyTagList).find((tag) => tag.orderWeight === orderWeight)without guarding againstnullentries left during Onyx merges.What changes do you think we should make in order to solve the problem?
In
src/libs/PolicyUtils/tag.ts(lines 16-33, 173-185):Boolean(tag)) ingetSortedTagKeys,getTagListName, andgetTagListByOrderWeight, and add a helperfindTagListEntryByOrderWeight(policyTagList: OnyxEntry<PolicyTagLists>, orderWeight: number)that returns[tagKey, tagList]for the matchingorderWeight:In
src/pages/workspace/tags/DynamicWorkspaceViewTagsPage.tsx(lines 84-87):currentPolicyTagdirectly from the entry matchingorderWeight(with a fallback to the last resolved renamed tag list key while focused if the server normalizesorderWeighton rename) instead of doingpolicyTags?.[currentTagListName]:In
src/libs/actions/Policy/Tag.ts(renamePolicyTagList,lines 981-1020):[oldKey, oldPolicyTags]usingfindTagListEntryByOrderWeight(policyTags, tagListIndex)(falling back to[oldName, policyTags?.[oldName]]), and explicitly setorderWeight: tagListIndexon[newName]inoptimisticData:In
src/pages/workspace/tags/WorkspaceTagsPage.tsx(line 328):${policyTagList.name}_${policyTagList.orderWeight}forkeyForListin multi-level mode so two tag lists with the same display name (Department) never collide on list item keys.What alternative solutions did you explore? (Optional)
nameinstead oforderWeight: rejected because dynamic tag routes (DYNAMIC_TAG_LIST_VIEW/DYNAMIC_TAGS_EDIT) are keyed by:orderWeightacross the entire app, and two lists can temporarily share the same display name (Department) right after appending a CSV. Resolving byorderWeightdirectly and preservingorderWeight: tagListIndexinrenamePolicyTagListfixes the bug cleanly without changing route schemas.suneox commentedon Oct 29, 2026
I can reproduce this bug and will troubleshoot it soon
suneox commentedon Oct 29, 2026
I looked into what happens after the import and after the rename.
1. Data after appending the CSV
The CSV's first column is named like the NetSuite list (
Department). After the append,policyTags_<policyID>has two lists with the samename. The BE stores the new one under a different key:nameorderWeightDepartmentexternalID: "Departments")Department_22. Why the rename ends on "Not here" (code at the time of the report)
/workspace-tag-list/2. The page convertsorderWeight2 to a name and looks the list up by that name, so it gets the NetSuite list (keyDepartment) and shows its 16 tags:DynamicWorkspaceViewTagsPage.tsx#L84-L87,getTagListNameorderWeight(1, not 2):DynamicWorkspaceViewTagsPage.tsx#L304RenamePolicyTaglistwitholdName: "Department", tagListIndex: 1:DynamicWorkspaceEditTagsPage.tsx#L67. The optimistic data removes the keyDepartment, i.e. the NetSuite list:Tag.ts#L984-L992externalID: "Departments"). The CSV list is still namedDepartment./workspace-tag-list/2, the name fororderWeight2 is stillDepartment, but the keyDepartmentno longer exists, socurrentPolicyTagis undefined and the page shows Not Found:DynamicWorkspaceViewTagsPage.tsx#L185So the request succeeds, but it renames the wrong list, and the NetSuite list's name gets changed.
3. Current
main#105212 (merged for #105010) now finds tag lists by
orderWeightand Onyx key on the App side:DynamicWorkspaceViewTagsPage.tsx#L84,renamePolicyTagList. With an equivalent local patch, the App opened the right list and sentoldName: "Dept CSV", tagListIndex: 2, but the BE still renamed the NetSuite list at index 1, then the page showed Not Found again. SoRenamePolicyTaglistfinds the list byoldNameand ignorestagListIndex, which #105212 also notes.@yuwenmemon I suggest fixing this in the BE append: it shouldn't create a list whose name already exists. Either reject the import with an error, or rename the new list (its
nametoo, not only the key). That matches the rule Classic's rename already has, stops duplicate lists from being created on both platforms, and keeps every name-based lookup in NewDot (tag pages, tag picker on expenses) unambiguous. What do you think?cc @trjExpensify
trjExpensify commentedon Oct 30, 2026
This sounds good to me 👍
yuwenmemon commentedon Oct 30, 2026
Well, technically, we do allow two tag lists with the same name. In fact, we added support for this here https://github.com/Expensify/Auth/pull/20714 about 7 months ago (stemming from https://github.com/Expensify/Expensify/issues/615983#issuecomment-4127986841). So blocking this wouldn't necessarily line up with how the feature is designed. We could start paying attention to the
tagListIndexApp sends to account for this... we're kind of ignoring it for the sake of the rename for now.