fix(agentex-ui): merge URL updates against the live URL to fix account-switch races#355
Merged
Merged
Conversation
…t-switch races updateParams rebuilt the query string from the useSearchParams() snapshot, which lags a render behind router.push. On an account switch two navigations fire — the provider clears task_id, the agent-validation effect clears agent_name — and whichever read the stale snapshot landed last, clobbering the other: intermittently resurrecting task_id, dropping agent_name, or reverting account_id while the sidebar already showed the new account's tasks. Merge each update against window.location.search (always current) so concurrent writes compose instead of overwrite, and clear task_id + agent_name atomically in the switch itself so it's a single authoritative navigation. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
dad58b2 to
dc3529b
Compare
declan-scale
approved these changes
Jul 10, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Switching accounts intermittently corrupted the URL state:
task_idwould survive the switch (leaving a stuck "phantom task" under the new account),agent_namewould clear, and sometimesaccount_ideven reverted to the old value while the sidebar already showed the new account's tasks.Root cause
updateParamsrebuilt the entire query string from theuseSearchParams()snapshot, which lags a render behindrouter.push. An account switch fires two navigations from two components:setSelectedAccountIdclearstask_idagentex-ui-rootclearsagent_nameWhichever call read the stale snapshot and landed last clobbered the other's change. The data layer switches synchronously (
selectedAccountIdRef), so tasks refetched for the new account even when the URL reverted — the smoking-gun symptom. It was intermittent because it depended on whetheruseSearchParamshad propagated between the two writes.Fix
use-safe-search-params.ts—updateParamsnow merges each update against the live URL (window.location.search), which is always current, so concurrent writes compose instead of overwrite. This is the systemic fix and addresses all three symptoms.agentex-provider.tsx— an explicit account switch now clearstask_idandagent_namein one atomic navigation (no dependence on a downstream effect, no flash of stale params).use-safe-search-params.test.tsx— regression test proving a write built from a stale snapshot no longer resurrectstask_id/account_id.Test plan
npm run typecheck✓npx vitest run— 50/50 ✓npm run lint✓Independent of #351 (OIDC); can merge in any order.
Greptile Summary
This PR fixes an intermittent URL-state corruption that occurred during account switches. The root cause was
updateParamsbuilding its query string from theuseSearchParams()snapshot, which lags behindrouter.push()calls — so two rapid navigations from different components would race and the last writer would clobber the first's changes.use-safe-search-params.ts:updateParamsnow readswindow.location.search(always current, updated synchronously by the History API) instead of the React snapshot, so concurrent writes compose rather than overwrite. Also fixes a minor trailing-?edge case for empty param sets.agentex-provider.tsx: The explicit account-switch now clears bothtask_idandagent_nameatomically in one navigation, eliminating the need for a downstream effect to clean upagent_name.use-safe-search-params.test.tsx: Regression tests proving the stale-snapshot clobber is gone, covering set/delete/preserve semantics andreplacevspushrouting.Confidence Score: 5/5
Safe to merge — the fix is narrowly scoped to URL merge behavior and is directly validated by new regression tests.
The change replaces a React snapshot read with a synchronous window.location.search read inside updateParams, which is always safe in browser environments and has a correct SSR fallback. The provider change and the new tests are consistent with the fix, and no existing behavior outside the race window is altered.
No files require special attention.
Important Files Changed
window.location.searchinstead of the staleuseSearchParams()snapshot so concurrent writes compose correctly. Also adds a clean fallback for SSR and fixes the trailing-?edge case for empty param sets.agent_name: nullalongsidetask_id: nullin the explicit-switch branch, so both account-scoped params are cleared atomically in one navigation rather than relying on a downstream effect.replacevspushrouting. Correctly usesvi.hoistedto share mock state with the factory closure.Sequence Diagram
%%{init: {'theme': 'neutral'}}%% sequenceDiagram participant P as AgentexProvider participant E as agentex-ui-root effect participant H as useSafeSearchParams participant W as window.location participant R as Next.js Router Note over P,R: Before fix - stale snapshot clobber P->>H: "updateParams({account_id:new, task_id:null})" H->>W: reads stale snapshot (account_id:old, task_id:T) H->>R: "push(?account_id=new&task_id=null)" R->>W: window.location.search updated Note over E: snapshot not yet updated E->>H: "updateParams({agent_name:null})" H->>W: reads stale snapshot (account_id:old, task_id:T) H->>R: "push(?account_id=old&task_id=T) clobbers first write" Note over P,R: After fix - live URL merge P->>H: "updateParams({account_id:new, task_id:null, agent_name:null})" H->>W: reads window.location.search (live) H->>R: "push(?account_id=new) atomic, one navigation" R->>W: window.location.search updated Note over E: effect fires but agent_name already cleared%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%% sequenceDiagram participant P as AgentexProvider participant E as agentex-ui-root effect participant H as useSafeSearchParams participant W as window.location participant R as Next.js Router Note over P,R: Before fix - stale snapshot clobber P->>H: "updateParams({account_id:new, task_id:null})" H->>W: reads stale snapshot (account_id:old, task_id:T) H->>R: "push(?account_id=new&task_id=null)" R->>W: window.location.search updated Note over E: snapshot not yet updated E->>H: "updateParams({agent_name:null})" H->>W: reads stale snapshot (account_id:old, task_id:T) H->>R: "push(?account_id=old&task_id=T) clobbers first write" Note over P,R: After fix - live URL merge P->>H: "updateParams({account_id:new, task_id:null, agent_name:null})" H->>W: reads window.location.search (live) H->>R: "push(?account_id=new) atomic, one navigation" R->>W: window.location.search updated Note over E: effect fires but agent_name already clearedReviews (2): Last reviewed commit: "fix(agentex-ui): merge URL updates again..." | Re-trigger Greptile