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-06-16] [$250] [Sentry: APP-DWR] Web – Crash in Copy Policy Settings when a tag group has no tags: Cannot convert undefined or null to object #91965
Version Number: 9.3.83-1 Reproducible in staging?: Yes Reproducible in production?: Not yet confirmed (introduced in staging, Copy Policy Settings is gated) Issue reported by: Internal Sentry investigation Sentry:https://expensify.sentry.io/issues/APP-DWR
Action Performed
Have a workspace where at least one tag group has no tags defined (tags is null or missing)
Navigate to Copy Policy Settings: /policy/<policyID>/copy-settings
Wait for the feature selection page to render
Expected Result
Copy Policy Settings page renders the feature list normally.
Actual Result
Full-page "Uh-oh, something went wrong!" ErrorBoundary crash. The app is completely unusable until the page is refreshed.
The reduce callback calls Object.values(tagGroup.tags) without guarding against a null or undefinedtags field, which can exist on tag groups that have no tags yet.
// Crashes when tagGroup.tags is null/undefined:Object.values(policyTags).reduce((sum,tagGroup)=>sum+Object.values(tagGroup.tags).filter((tag)=>tag.pendingAction!==CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE).length,0)// Fix: guard tagGroup.tags before calling Object.values()Object.values(tagGroup.tags??{}).filter(...)
Please re-state the problem that we are trying to solve in this issue.
Opening Copy Policy Settings (/policy/<policyID>/copy-settings) crashes the whole page with the "Uh-oh, something went wrong!" ErrorBoundary when the source workspace has a tag group whose tags field is null/missing (a tag list created with no tags yet). The page is unusable until refresh.
What is the root cause of that problem?
CopyPolicySettingsSelectFeaturesPage computes totalTags during render and calls Object.values() on each tag group's tagswithout guarding the argument:
Object.values(tagGroup.tags) throws TypeError: Cannot convert undefined or null to object the moment tagGroup.tags is null/undefined. Because this runs synchronously in the render body, the throw bubbles straight to the ErrorBoundary → full-page crash.
The tell is that every other count in this same block already guards its argument with ?? {} — categories (L63), taxes (L67), report fields (L68), distance rates (L70), per-diem (L71). Only the nested tagGroup.tags lookup on L65 was missed, so empty tag groups slip through.
What changes do you think we should make in order to solve the problem?
Guard the argument inside Object.values(), matching the convention used on every adjacent line of the same function:
This is the canonical app-wide pattern for reading tag lists — e.g. PolicyUtils.ts:914, WorkspaceTagsPage.tsx:238, SearchQueryUtils.ts:1176 all use Object.values(<list>.tags ?? {}). Scope is the single L65 expression; no data-layer or type changes.
Also fix the same latent crash in the analog Duplicate-Workspace flow that Copy Policy Settings was modeled on:
That code looks guarded but is not — Object.values(tagGroup.tags)?.filter(...).length ?? 0 places the optional chain afterObject.values() has already thrown, so a null tag group crashes the Duplicate Workspace feature page identically. Apply the same ?? {} fix there.
Verification: create a workspace with one empty tag group (no tags), open /policy/<policyID>/copy-settings → before, ErrorBoundary crash; after, page renders and the empty group contributes 0 to the tag count. Same check on the Duplicate Workspace feature page.
What alternative solutions did you explore? (Optional)
Copy the existing Duplicate-Workspace patternObject.values(tagGroup.tags)?.filter(...).length ?? 0 — rejected: the optional chain runs after Object.values() already threw, so it never prevents the crash. It is the latent bug, not a fix.
Wrap totalTags in a try/catch or guarded useMemo — rejected: masks the null shape instead of handling it, and is inconsistent with the surrounding lines that all simply use ?? {}.
Normalize tags to {} at the Onyx/data layer — rejected: large blast radius across every tag consumer; empty tag groups are legitimate data, and the app convention is to guard at the read site.
⚠️@yusufdeveloper2903 Thanks for your proposal. Please update it to follow the proposal template, as proposals are only reviewed if they follow that format (note the mandatory sections).
Please re-state the problem that we are trying to solve in this issue.
On Web, opening Copy Policy Settings (/policy/<policyID>/copy-settings) for a workspace where a tag group has no tags (tags is null/undefined) triggers a full-page ErrorBoundary crash ("Uh-oh, something went wrong!"), making the app unusable until refresh. Expected: the feature selection page renders the feature list normally, counting such a tag group as contributing zero tags.
What is the root cause of that problem?
In src/pages/workspace/copyPolicySettings/CopyPolicySettingsSelectFeaturesPage.tsx:65, the totalTags reduce callback calls Object.values(tagGroup.tags) directly. When a tag group has no tags, tagGroup.tags is null/undefined, and Object.values(null) throws TypeError: Cannot convert undefined or null to object, which propagates up through render to the ErrorBoundary. Every sibling count in the same block (lines 63, 67, 68, 70) already guards with Object.values(... ?? {}); this one site was missed.
What changes do you think we should make in order to solve the problem?
In CopyPolicySettingsSelectFeaturesPage.tsx:65, change Object.values(tagGroup.tags) to Object.values(tagGroup.tags ?? {}), mirroring the existing guard pattern used by the surrounding counts on lines 63, 67, 68, and 70.
What alternative solutions did you explore? (Optional)
Navigating to /policy/<policyID>/copy-settings on a workspace that has at least one tag group with no tags (i.e. tags is null/undefined on the Onyx record) crashes the page with TypeError: Cannot convert undefined or null to object, throwing the user into the full-page ErrorBoundary.
Root cause
CopyPolicySettingsSelectFeaturesPage.tsx computes totalTags by reducing every tag group and calling Object.values(tagGroup.tags) directly. Object.values() throws synchronously when its argument is null/undefined, so a single tag group with an empty tags field is enough to take the whole render down.
Source at src/pages/workspace/copyPolicySettings/CopyPolicySettingsSelectFeaturesPage.tsx:64-66:
While the PolicyTagList Onyx type declares tags: PolicyTags as non-optional (src/types/onyx/PolicyTag.ts:41-59), the runtime data does not honor that — every other consumer in the codebase guards the field. For example, src/libs/PolicyUtils.ts:811-813:
The same ?? {} guard is used in at least src/libs/SearchQueryUtils.ts:1176, src/hooks/useAdvancedSearchFilters.ts:287, src/pages/settings/Rules/Fields/AddTagPage.tsx:34, and src/pages/workspace/tags/WorkspaceTagsPage.tsx:238,249,256,282. The CopyPolicySettings page is the outlier that omitted it.
1 - CopyPolicySettingsSelectFeaturesPage crashes on Object.values(tagGroup.tags) — the bug reported in the issue.
2 - The sister page WorkspaceDuplicateSelectFeaturesForm has the same crash with a flawed "half-fix" — at src/pages/workspace/duplicate/WorkspaceDuplicateSelectFeaturesForm.tsx:66-74:
The ?.filter optional-chain looks defensive but is unreachable: Object.values() throws synchronously on null/undefined before it can return anything to chain against. Any workspace that crashes Copy Policy Settings will also crash Workspace Duplicate via /policy/<policyID>/duplicate. Both should be fixed together since they share the exact bug pattern and the user-facing surface is identical (Sentry will start reporting WorkspaceDuplicateSelectFeaturesForm next).
Proposed fix
Add the ?? {} guard directly inside the Object.values() call so the reducer treats a tag group with no tags field as contributing zero.
The Object.values(x.tags ?? {}) pattern is already the established convention in this codebase (8+ call sites, including the canonical hasTags and getTagNamesFromTagsLists helpers in PolicyUtils.ts), so this change brings the two outliers into line with the rest of the policy-tags consumers. It is a minimal, in-place fix that does not change the PolicyTagList type definition — flipping tags to optional there would ripple through many files and is not justified by a single crash. A wrapper helper (getTagsCount(policyTags)) was considered but rejected because the two existing call sites differ slightly (one is memoized, one is not, and only the duplicate page early-returns on !policyTags) and abstracting them now would be premature for a two-line guard. The fix is also consistent with the issue author's suggested patch.
In CopyPolicySettingsSelectFeaturesPage.tsx:65, Object.values(tagGroup.tags) is called inside a reduce without guarding against tagGroup.tags being null or undefined. The outer policyTags is null-checked, but individual tag groups can have missing tags fields at runtime (e.g., a workspace with an empty tag group). Object.values(null) throws a TypeError, which propagates to the ErrorBoundary and crashes the page.
The TypeScript type (PolicyTagList.tags: PolicyTags) is declared as required, so the compiler doesn't flag it — but Onyx data from the server can have missing fields at runtime, which is why 10+ other call sites in the codebase already guard with ?? {}.
What changes do you think we should make in order to solve the problem?
Add a ?? {} fallback on tagGroup.tags so Object.values receives an empty object instead of throwing:
The same unguarded pattern also exists in WorkspaceDuplicateSelectFeaturesForm.tsx:71 — the ?. after Object.values(tagGroup.tags) does NOT protect against tagGroup.tags being nullish (it chains on the result array, but Object.values(null) already throws before that). Apply the same ?? {} fix there.
This matches the defensive pattern used consistently throughout the codebase (e.g., PolicyUtils.ts, TagPicker, SearchQueryUtils, TransactionUtils).
What alternative solutions did you explore? (Optional)
Could make tags optional in the PolicyTagList type (tags?: PolicyTags), but that would require updating every access site and doesn't match the existing codebase convention of guarding with ?? {} at call sites.
Next Steps for Contributor+ team: To accept:@MelvinBot implement this to create a draft PR. To refine:@MelvinBot <your feedback> To reject: Explain why you are rejecting Melvin's proposal.
Activity
melvin-bot commentedon May 28, 2026
Triggered auto assignment to Contributor-plus team member for initial proposal review - @ChavdaSachin (
External)melvin-bot commentedon May 28, 2026
Job added to Upwork: https://www.upwork.com/jobs/~022059992332026743806
yusufdeveloper2903 commentedon May 28, 2026
Proposal
Please re-state the problem that we are trying to solve in this issue.
Opening Copy Policy Settings (
/policy/<policyID>/copy-settings) crashes the whole page with the "Uh-oh, something went wrong!" ErrorBoundary when the source workspace has a tag group whosetagsfield isnull/missing (a tag list created with no tags yet). The page is unusable until refresh.What is the root cause of that problem?
CopyPolicySettingsSelectFeaturesPagecomputestotalTagsduring render and callsObject.values()on each tag group'stagswithout guarding the argument:App/src/pages/workspace/copyPolicySettings/CopyPolicySettingsSelectFeaturesPage.tsx
Lines 64 to 66 in 5eb8701
Object.values(tagGroup.tags)throwsTypeError: Cannot convert undefined or null to objectthe momenttagGroup.tagsisnull/undefined. Because this runs synchronously in the render body, the throw bubbles straight to the ErrorBoundary → full-page crash.The tell is that every other count in this same block already guards its argument with
?? {}— categories (L63), taxes (L67), report fields (L68), distance rates (L70), per-diem (L71). Only the nestedtagGroup.tagslookup on L65 was missed, so empty tag groups slip through.What changes do you think we should make in order to solve the problem?
Guard the argument inside
Object.values(), matching the convention used on every adjacent line of the same function:This is the canonical app-wide pattern for reading tag lists — e.g.
PolicyUtils.ts:914,WorkspaceTagsPage.tsx:238,SearchQueryUtils.ts:1176all useObject.values(<list>.tags ?? {}). Scope is the single L65 expression; no data-layer or type changes.Also fix the same latent crash in the analog Duplicate-Workspace flow that Copy Policy Settings was modeled on:
App/src/pages/workspace/duplicate/WorkspaceDuplicateSelectFeaturesForm.tsx
Lines 70 to 73 in 5eb8701
That code looks guarded but is not —
Object.values(tagGroup.tags)?.filter(...).length ?? 0places the optional chain afterObject.values()has already thrown, so anulltag group crashes the Duplicate Workspace feature page identically. Apply the same?? {}fix there.Verification: create a workspace with one empty tag group (no tags), open
/policy/<policyID>/copy-settings→ before, ErrorBoundary crash; after, page renders and the empty group contributes0to the tag count. Same check on the Duplicate Workspace feature page.What alternative solutions did you explore? (Optional)
Object.values(tagGroup.tags)?.filter(...).length ?? 0— rejected: the optional chain runs afterObject.values()already threw, so it never prevents the crash. It is the latent bug, not a fix.totalTagsin atry/catchor guardeduseMemo— rejected: masks thenullshape instead of handling it, and is inconsistent with the surrounding lines that all simply use?? {}.tagsto{}at the Onyx/data layer — rejected: large blast radius across every tag consumer; empty tag groups are legitimate data, and the app convention is to guard at the read site.github-actions commentedon May 28, 2026
aswin-s commentedon May 28, 2026
Proposal
Please re-state the problem that we are trying to solve in this issue.
On Web, opening Copy Policy Settings (
/policy/<policyID>/copy-settings) for a workspace where a tag group has no tags (tagsisnull/undefined) triggers a full-page ErrorBoundary crash ("Uh-oh, something went wrong!"), making the app unusable until refresh. Expected: the feature selection page renders the feature list normally, counting such a tag group as contributing zero tags.What is the root cause of that problem?
In
src/pages/workspace/copyPolicySettings/CopyPolicySettingsSelectFeaturesPage.tsx:65, thetotalTagsreduce callback callsObject.values(tagGroup.tags)directly. When a tag group has no tags,tagGroup.tagsisnull/undefined, andObject.values(null)throwsTypeError: Cannot convert undefined or null to object, which propagates up through render to the ErrorBoundary. Every sibling count in the same block (lines 63, 67, 68, 70) already guards withObject.values(... ?? {}); this one site was missed.What changes do you think we should make in order to solve the problem?
CopyPolicySettingsSelectFeaturesPage.tsx:65, changeObject.values(tagGroup.tags)toObject.values(tagGroup.tags ?? {}), mirroring the existing guard pattern used by the surrounding counts on lines 63, 67, 68, and 70.What alternative solutions did you explore? (Optional)
trasnake87 commentedon May 28, 2026
Proposal
Navigating to
/policy/<policyID>/copy-settingson a workspace that has at least one tag group with no tags (i.e.tagsisnull/undefinedon the Onyx record) crashes the page withTypeError: Cannot convert undefined or null to object, throwing the user into the full-pageErrorBoundary.Root cause
CopyPolicySettingsSelectFeaturesPage.tsxcomputestotalTagsby reducing every tag group and callingObject.values(tagGroup.tags)directly.Object.values()throws synchronously when its argument isnull/undefined, so a single tag group with an emptytagsfield is enough to take the whole render down.Source at
src/pages/workspace/copyPolicySettings/CopyPolicySettingsSelectFeaturesPage.tsx:64-66:While the
PolicyTagListOnyx type declarestags: PolicyTagsas non-optional (src/types/onyx/PolicyTag.ts:41-59), the runtime data does not honor that — every other consumer in the codebase guards the field. For example,src/libs/PolicyUtils.ts:811-813:and
src/libs/PolicyUtils.ts:928-936:The same
?? {}guard is used in at leastsrc/libs/SearchQueryUtils.ts:1176,src/hooks/useAdvancedSearchFilters.ts:287,src/pages/settings/Rules/Fields/AddTagPage.tsx:34, andsrc/pages/workspace/tags/WorkspaceTagsPage.tsx:238,249,256,282. The CopyPolicySettings page is the outlier that omitted it.1 - CopyPolicySettingsSelectFeaturesPage crashes on
Object.values(tagGroup.tags)— the bug reported in the issue.2 - The sister page
WorkspaceDuplicateSelectFeaturesFormhas the same crash with a flawed "half-fix" — atsrc/pages/workspace/duplicate/WorkspaceDuplicateSelectFeaturesForm.tsx:66-74:The
?.filteroptional-chain looks defensive but is unreachable:Object.values()throws synchronously onnull/undefinedbefore it can return anything to chain against. Any workspace that crashes Copy Policy Settings will also crash Workspace Duplicate via/policy/<policyID>/duplicate. Both should be fixed together since they share the exact bug pattern and the user-facing surface is identical (Sentry will start reportingWorkspaceDuplicateSelectFeaturesFormnext).Proposed fix
Add the
?? {}guard directly inside theObject.values()call so the reducer treats a tag group with notagsfield as contributing zero.Apply the same guard to the duplicate page and drop the dead
?./?? 0since.lengthalready returns a number:Why this approach
The
Object.values(x.tags ?? {})pattern is already the established convention in this codebase (8+ call sites, including the canonicalhasTagsandgetTagNamesFromTagsListshelpers inPolicyUtils.ts), so this change brings the two outliers into line with the rest of the policy-tags consumers. It is a minimal, in-place fix that does not change thePolicyTagListtype definition — flippingtagsto optional there would ripple through many files and is not justified by a single crash. A wrapper helper (getTagsCount(policyTags)) was considered but rejected because the two existing call sites differ slightly (one is memoized, one is not, and only the duplicate page early-returns on!policyTags) and abstracting them now would be premature for a two-line guard. The fix is also consistent with the issue author's suggested patch.MelvinBot commentedon May 28, 2026
Proposal
What is the root cause of that problem?
In
CopyPolicySettingsSelectFeaturesPage.tsx:65,Object.values(tagGroup.tags)is called inside areducewithout guarding againsttagGroup.tagsbeingnullorundefined. The outerpolicyTagsis null-checked, but individual tag groups can have missingtagsfields at runtime (e.g., a workspace with an empty tag group).Object.values(null)throws aTypeError, which propagates to the ErrorBoundary and crashes the page.The TypeScript type (
PolicyTagList.tags: PolicyTags) is declared as required, so the compiler doesn't flag it — but Onyx data from the server can have missing fields at runtime, which is why 10+ other call sites in the codebase already guard with?? {}.What changes do you think we should make in order to solve the problem?
Add a
?? {}fallback ontagGroup.tagssoObject.valuesreceives an empty object instead of throwing:In
CopyPolicySettingsSelectFeaturesPage.tsx:65: changeObject.values(tagGroup.tags)→Object.values(tagGroup.tags ?? {})The same unguarded pattern also exists in
WorkspaceDuplicateSelectFeaturesForm.tsx:71— the?.afterObject.values(tagGroup.tags)does NOT protect againsttagGroup.tagsbeing nullish (it chains on the result array, butObject.values(null)already throws before that). Apply the same?? {}fix there.This matches the defensive pattern used consistently throughout the codebase (e.g.,
PolicyUtils.ts,TagPicker,SearchQueryUtils,TransactionUtils).What alternative solutions did you explore? (Optional)
Could make
tagsoptional in thePolicyTagListtype (tags?: PolicyTags), but that would require updating every access site and doesn't match the existing codebase convention of guarding with?? {}at call sites.Next Steps for Contributor+ team:
To accept:
@MelvinBot implement thisto create a draft PR.To refine:
@MelvinBot <your feedback>To reject: Explain why you are rejecting Melvin's proposal.
ChavdaSachin commentedon May 28, 2026
@MelvinBot, please implement this
MelvinBot commentedon May 28, 2026
🤖 Draft PR created: #91968
Please mark it as "Ready for review" when it is ready for review.
21 remaining items
fix outstanding issues
melvin-bot commentedon Jun 15, 2026
Triggered auto assignment to @mallenexpensify (
Awaiting Payment)melvin-bot commentedon Jun 15, 2026
Payment Summary
Upwork Job
BugZero Checklist (@mallenexpensify)
mallenexpensify commentedon Jun 17, 2026
Payment Summary
Contributor+: @ChavdaSachin due $250 via NewDot
This doesn't look like it's paid via project so I'm paying. Comment/reopen if otherwise.
quinthar commentedon Jun 29, 2026
Approved $250 to @ChavdaSachin