Skip to content

fix(agentex-ui): merge URL updates against the live URL to fix account-switch races#355

Merged
erichwoo-scale merged 1 commit into
mainfrom
fix/agentex-ui-account-switch-url-race
Jul 10, 2026
Merged

fix(agentex-ui): merge URL updates against the live URL to fix account-switch races#355
erichwoo-scale merged 1 commit into
mainfrom
fix/agentex-ui-account-switch-url-race

Conversation

@erichwoo-scale

@erichwoo-scale erichwoo-scale commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

Problem

Switching accounts intermittently corrupted the URL state: task_id would survive the switch (leaving a stuck "phantom task" under the new account), agent_name would clear, and sometimes account_id even reverted to the old value while the sidebar already showed the new account's tasks.

Root cause

updateParams rebuilt the entire query string from the useSearchParams() snapshot, which lags a render behind router.push. An account switch fires two navigations from two components:

  • the provider's setSelectedAccountId clears task_id
  • the agent-validation effect in agentex-ui-root clears agent_name

Whichever 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 whether useSearchParams had propagated between the two writes.

Fix

  • use-safe-search-params.tsupdateParams now 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 clears task_id and agent_name in 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 resurrects task_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 updateParams building its query string from the useSearchParams() snapshot, which lags behind router.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: updateParams now reads window.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 both task_id and agent_name atomically in one navigation, eliminating the need for a downstream effect to clean up agent_name.
  • use-safe-search-params.test.tsx: Regression tests proving the stale-snapshot clobber is gone, covering set/delete/preserve semantics and replace vs push routing.

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

Filename Overview
agentex-ui/hooks/use-safe-search-params.ts Core fix: reads from window.location.search instead of the stale useSearchParams() snapshot so concurrent writes compose correctly. Also adds a clean fallback for SSR and fixes the trailing-? edge case for empty param sets.
agentex-ui/components/providers/agentex-provider.tsx Adds agent_name: null alongside task_id: null in the explicit-switch branch, so both account-scoped params are cleared atomically in one navigation rather than relying on a downstream effect.
agentex-ui/hooks/use-safe-search-params.test.tsx New regression test suite with three cases: stale-snapshot clobber prevention, combined set/delete/preserve in one update, and replace vs push routing. Correctly uses vi.hoisted to 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
Loading
%%{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 cleared
Loading

Reviews (2): Last reviewed commit: "fix(agentex-ui): merge URL updates again..." | Re-trigger Greptile

…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>
@erichwoo-scale erichwoo-scale force-pushed the fix/agentex-ui-account-switch-url-race branch from dad58b2 to dc3529b Compare July 10, 2026 04:41
@erichwoo-scale erichwoo-scale merged commit f87e363 into main Jul 10, 2026
15 checks passed
@erichwoo-scale erichwoo-scale deleted the fix/agentex-ui-account-switch-url-race branch July 10, 2026 16:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants