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
[Exploratory] Workspace - App crashes when reopening workspace settings link #98300
The console log attached to this issue names the crash exactly:
TypeError: Cannot read properties of undefined (reading 'backTo')
at WorkspaceOverviewPage
src/pages/workspace/WorkspaceOverviewPage.tsx:97-98 reads route.params.backTo and route.params.policyID with no guard. On the second open of the link, route.params is undefined, so the component throws on its first statement — before any render guard can run.
route.params becomes undefined because WORKSPACE.PROFILE is the defaultCentralScreen of the WorkspaceSplitNavigator (WorkspaceSplitNavigator.tsx:57). When the split navigator is re-entered without its nested state, the router falls back to an initial state holding only the sidebar route, whose params are deliberately undefined. SplitRouter.ts:87-90 then injects the central screen with params: state.routes.at(0)?.params — copying that undefined straight onto WORKSPACE.PROFILE.
The first click works and the second crashes because the two clicks take different branches in linkTo: the first sees an unvisited Workspaces tab and dispatches the full nested state; the repeat visit does not.
TypeScript does not catch this. types.ts:2901-2905 types WORKSPACE.PROFILE params as {policyID: string; backTo?: Routes} without | undefined, which makes React Navigation's conditional Route type mark params as required. The type does not match runtime.
What changes do you think we should make in order to solve the problem?
Guard the crashing reads in WorkspaceOverviewPage.tsx — use optional chaining for backTo and policyID at lines 97-98 (and the same reads at lines 349 and 769), and skip the policy fetch when policyID is missing. This is exactly the pattern already applied to the sibling sidebar screen in WorkspaceInitialPage.tsx:86 by Fix workspace navigation after distance settings deep link #98423, and the wrapper this page already renders through, withPolicy.tsx:79, optional-chains the same route object.
Fix the type so this class of bug is visible. Widen the split-navigator param list entry so params is optional for WORKSPACE.PROFILE. Without this, adding ?. looks like dead code to a reviewer and will get removed again.
Preserve the policyID at the router level so the central screen is not synthesised paramless — SplitRouter.ts:87-90 should carry the sidebar's policyID rather than copying a possibly-undefined params object.
Step 1 stops the crash. Steps 2 and 3 matter because 20 of the 22 screens registered in WorkspaceSplitNavigator read route.params unguarded — only WorkspaceInitialPage and PolicyAccountingPage are defensive. A page-local fix leaves the rest as latent crash sites.
What alternative solutions did you explore? (Optional)
Alternatives considered, evidence, and verification notes
Alternative: treat this as already fixed.#98423 (merged 2026-08-31) repaired the linkTo path that dropped the split navigator's nested state — the same mechanism described above, and its PR body describes the identical /workspaces/undefined symptom. This issue was reported on 2026-08-11 against 9.4.52-0, which predates that fix. I could not rule this out as the reason: the automated web test run for this analysis failed on an infrastructure problem (the browser session never rendered), so I have no live staging confirmation either way. Even if #98423 removed this particular trigger, the unguarded read at line 97 and the incorrect type are still on main today, so the crash remains one navigation change away.
Alternative: an error boundary or a not-found page. That converts a crash into a broken screen without fixing the missing policyID, and #69850 already tracks making error pages recoverable. Not a substitute for the guard.
Alternative: a useEffect guard. Does not work here — the throw happens in the component body at line 97, before any effect runs.
Supporting evidence that params: undefined is a real runtime state: a merged test, tests/ui/WorkspaceInitialPageTest.tsx:96-101, explicitly mocks {route: {params: undefined}} for a screen in this navigator.
Git history: the unguarded const backTo = route.params.backTo; was introduced in commit 113d584b (#56204, 2025-01-16) in the then-named WorkspaceProfilePage.tsx and carried through the rename in #56920. It was never optional-chained, so no guard was removed. route.params.policyID at line 98 was added by #89414 (2026-05-08) to satisfy the no-default-id-values lint rule, which replaced a nullish-coalesced read with a raw one.
Related issues: a quick scan of open App issues found no duplicate. #69850 (error pages should be recoverable) is loosely related but not the same bug.
Next Steps for Contributor+ team: To accept:@MelvinBot implement [this](https://github.com/Expensify/App/issues/98300) to create a draft PR. To refine:@MelvinBot <your feedback> To reject: Explain why you are rejecting Melvin's proposal.
⏱️ Ran out of time before I could finish. This usually means the task was too complex or I got stuck exploring. Please try again with a more specific request or post in #melvinbot-ai.
mananjadhav — yes. On current main the root cause is the same and the fix still works. No merged or open PR has fixed this. Two parts of the proposal need edits: item 1's code moved to a new file, and the item 5 test-harness change has already landed. All links use SHA de7716f.
All three SplitRouter backfill sites are still needed. Lines shifted about +4: the sidebar is at 93-106 and the central screen at 120-123. Two PRs touched the file since, #101905 and #103746, but neither changed params.
4
✅ Same, plus 2 lines
fetchPolicyData still has no !routePolicyID guard. Also add ?. at line 407 (clearInviteDraft) and line 951 (RULES_CUSTOM). DomainMembersPage is now at line 57, still unguarded.
I searched merged and open PRs since 2026-09-15 for SplitRouter, WorkspaceSplitNavigator, splitNavigatorParams, getInitialSplitNavigatorState, and 98300. None of them handles split-route params.
Possible follow-up: hasDifferentSplitScope.ts:23-34 reads scope only from nested routes. It would ignore the new top-level policyID, which is harmless.
I tried the 11-step repro on web again, but the test session loaded a blank page during onboarding. Live confirmation is still open.
Next Steps for Contributor+ team: To accept:@MelvinBot implement [this](https://github.com/Expensify/App/issues/98300#issuecomment-5686026959) with the updates in my latest comment to create a draft PR. To refine:@MelvinBot <your feedback>
Activity
applause-bot commentedon Aug 11, 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/ef6cd7d917f1451a1c1d30869832bd35f77eab339a2a1e2683bca48c3d7044e9
https://platform.applause.com/services/links/v1/external/ed894168cfee663a4cbb41ddd41e0e069c156ae8c2added2c5e79e1515cdffab
MelvinBot commentedon Sep 5, 2026
Proposal
What is the root cause of that problem?
The console log attached to this issue names the crash exactly:
src/pages/workspace/WorkspaceOverviewPage.tsx:97-98readsroute.params.backToandroute.params.policyIDwith no guard. On the second open of the link,route.paramsisundefined, so the component throws on its first statement — before any render guard can run.route.paramsbecomesundefinedbecauseWORKSPACE.PROFILEis thedefaultCentralScreenof theWorkspaceSplitNavigator(WorkspaceSplitNavigator.tsx:57). When the split navigator is re-entered without its nested state, the router falls back to an initial state holding only the sidebar route, whose params are deliberatelyundefined.SplitRouter.ts:87-90then injects the central screen withparams: state.routes.at(0)?.params— copying thatundefinedstraight ontoWORKSPACE.PROFILE.The first click works and the second crashes because the two clicks take different branches in
linkTo: the first sees an unvisited Workspaces tab and dispatches the full nested state; the repeat visit does not.TypeScript does not catch this.
types.ts:2901-2905typesWORKSPACE.PROFILEparams as{policyID: string; backTo?: Routes}without| undefined, which makes React Navigation's conditionalRoutetype markparamsas required. The type does not match runtime.What changes do you think we should make in order to solve the problem?
Guard the crashing reads in
WorkspaceOverviewPage.tsx— use optional chaining forbackToandpolicyIDat lines 97-98 (and the same reads at lines 349 and 769), and skip the policy fetch whenpolicyIDis missing. This is exactly the pattern already applied to the sibling sidebar screen inWorkspaceInitialPage.tsx:86by Fix workspace navigation after distance settings deep link #98423, and the wrapper this page already renders through,withPolicy.tsx:79, optional-chains the same route object.Fix the type so this class of bug is visible. Widen the split-navigator param list entry so
paramsis optional forWORKSPACE.PROFILE. Without this, adding?.looks like dead code to a reviewer and will get removed again.Preserve the
policyIDat the router level so the central screen is not synthesised paramless —SplitRouter.ts:87-90should carry the sidebar'spolicyIDrather than copying a possibly-undefinedparams object.Step 1 stops the crash. Steps 2 and 3 matter because 20 of the 22 screens registered in
WorkspaceSplitNavigatorreadroute.paramsunguarded — onlyWorkspaceInitialPageandPolicyAccountingPageare defensive. A page-local fix leaves the rest as latent crash sites.What alternative solutions did you explore? (Optional)
Alternatives considered, evidence, and verification notes
Alternative: treat this as already fixed. #98423 (merged 2026-08-31) repaired the
linkTopath that dropped the split navigator's nested state — the same mechanism described above, and its PR body describes the identical/workspaces/undefinedsymptom. This issue was reported on 2026-08-11 against 9.4.52-0, which predates that fix. I could not rule this out as the reason: the automated web test run for this analysis failed on an infrastructure problem (the browser session never rendered), so I have no live staging confirmation either way. Even if #98423 removed this particular trigger, the unguarded read at line 97 and the incorrect type are still onmaintoday, so the crash remains one navigation change away.Alternative: an error boundary or a not-found page. That converts a crash into a broken screen without fixing the missing
policyID, and #69850 already tracks making error pages recoverable. Not a substitute for the guard.Alternative: a
useEffectguard. Does not work here — the throw happens in the component body at line 97, before any effect runs.Supporting evidence that
params: undefinedis a real runtime state: a merged test,tests/ui/WorkspaceInitialPageTest.tsx:96-101, explicitly mocks{route: {params: undefined}}for a screen in this navigator.Git history: the unguarded
const backTo = route.params.backTo;was introduced in commit113d584b(#56204, 2025-01-16) in the then-namedWorkspaceProfilePage.tsxand carried through the rename in #56920. It was never optional-chained, so no guard was removed.route.params.policyIDat line 98 was added by #89414 (2026-05-08) to satisfy theno-default-id-valueslint rule, which replaced a nullish-coalesced read with a raw one.Related issues: a quick scan of open App issues found no duplicate. #69850 (error pages should be recoverable) is loosely related but not the same bug.
Next Steps for Contributor+ team:
To accept:
@MelvinBot implement [this](https://github.com/Expensify/App/issues/98300)to create a draft PR.To refine:
@MelvinBot <your feedback>To reject: Explain why you are rejecting Melvin's proposal.
view run · no recording available
melvin-bot commentedon Sep 5, 2026
Triggered auto assignment to Contributor-plus team member for initial proposal review - @mananjadhav (
External)melvin-bot commentedon Sep 5, 2026
Unable to auto-create job on Upwork. The BZ team member should create it manually for this issue.
mananjadhav commentedon Sep 6, 2026
Reviewing.
melvin-bot commentedon Sep 8, 2026
@mananjadhav this issue is now 4 weeks old, please consider:
Thanks!
11 remaining items
mananjadhav commentedon Sep 22, 2026
@MelvinBot implement this
MelvinBot commentedon Sep 22, 2026
⏱️ Ran out of time before I could finish. This usually means the task was too complex or I got stuck exploring. Please try again with a more specific request or post in #melvinbot-ai.
Run details
melvin-bot commentedon Sep 25, 2026
@mananjadhav Whoops! This issue is 2 days overdue. Let's get this updated quick!
melvin-bot commentedon Sep 29, 2026
@mananjadhav 6 days overdue. This is scarier than being forced to listen to Vogon poetry!
melvin-bot commentedon Oct 1, 2026
@mananjadhav Now this issue is 8 days overdue. Are you sure this should be a Daily? Feel free to change it!
melvin-bot commentedon Oct 5, 2026
@mananjadhav 12 days overdue now... This issue's end is nigh!
melvin-bot commentedon Oct 8, 2026
This issue has not been updated in over 14 days. @mananjadhav eroding to Weekly issue.
mananjadhav commentedon Oct 23, 2026
@MelvinBot Check this comment again #98300 (comment) and see if we have the same update and fix.
MelvinBot commentedon Oct 23, 2026
mananjadhav— yes. On currentmainthe root cause is the same and the fix still works. No merged or open PR has fixed this. Two parts of the proposal need edits: item 1's code moved to a new file, and the item 5 test-harness change has already landed. All links use SHAde7716f.Still broken on
main:SplitRouter.ts:190still falls back tostackRouter.getInitialState().SplitRouter.ts:122still copiesundefinedparams onto the central screen.WorkspaceOverviewPage.tsx:121-122still throws.What changed in each item:
getMatchingFullScreenRoute.ts:157-166(workspace) and:190-199(domain). Neither call passessplitNavigatorParamsyet.linkToandswapBackgroundTabForRHPTargetcall this function too, so the fix covers them as well.getInitialSplitNavigatorState.ts:14is stillRecord<string, string>.types.tswas split up, so the split-navigator entries are now attypes.ts:1322-1323.SplitRouterbackfill sites are still needed. Lines shifted about +4: the sidebar is at 93-106 and the central screen at 120-123. Two PRs touched the file since, #101905 and #103746, but neither changed params.fetchPolicyDatastill has no!routePolicyIDguard. Also add?.at line 407 (clearInviteDraft) and line 951 (RULES_CUSTOM).DomainMembersPageis now at line 57, still unguarded.routeasparentRoute(TestNavigationContainer.tsx:85). It also added a domain split harness. Drop the harness change and keep only the regression test. The newtests/unit/Navigation/SplitRouterTest.tsis a good place for it.Other notes
SplitRouter,getInitialSplitNavigatorState, or the Overview page.SplitRouter,WorkspaceSplitNavigator,splitNavigatorParams,getInitialSplitNavigatorState, and98300. None of them handles split-route params.hasDifferentSplitScope.ts:23-34reads scope only from nested routes. It would ignore the new top-levelpolicyID, which is harmless.Next Steps for Contributor+ team:
To accept:
@MelvinBot implement [this](https://github.com/Expensify/App/issues/98300#issuecomment-5686026959) with the updates in my latest commentto create a draft PR.To refine:
@MelvinBot <your feedback>view run · no recording available