jlcodes99/cockpit-tools · error

CODEX_STALE_ACCOUNT

CODEX_STALE_ACCOUNT

Error message

CODEX_STALE_ACCOUNT

What it means

switchAccount in the Codex account store throws CODEX_STALE_ACCOUNT when the requested accountId no longer exists in the store's accounts list — i.e. the caller asked to switch to an account that was deleted or removed by a concurrent fetch. Before throwing it refreshes the current account so local state converges with reality.

Source

Thrown at src/stores/useCodexAccountStore.ts:261

      accountId,
    });
    const accounts = await codexService.listCodexAccounts();
    console.info('[Codex Switch][Store] listCodexAccounts finished', {
      accountId,
      elapsedMs: Math.round(performance.now() - flowStartedAt),
    });
    // Drop any in-flight fetch results before applying mutation state.
    invalidateCodexFetchRequests();
    set({ accounts, accountsLoaded: true, loading: false, error: null });
    persistCodexAccountsCache(accounts);

    const targetExists = accounts.some((account) => account.id === accountId);
    if (!targetExists) {
      const currentAccount = await codexService.getCurrentCodexAccount();
      invalidateCodexFetchRequests();
      set({ currentAccount });
      persistCodexCurrentAccountCache(currentAccount);
      throw new Error(CODEX_STALE_ACCOUNT_ERROR);
    }

    let account: CodexAccount;
    try {
      account = await codexService.switchCodexAccount(accountId, {
        reauthTokenGeneration: options?.reauthTokenGeneration,
        launchAfterSwitch: options?.launchAfterSwitch,
      });
    } catch (error) {
      // Token Authority 可能已把账号标记为 requires_reauth。立即回读账号库,
      // 让账号卡片和切号弹框都展示最新的 API-only / 需授权状态。
      void get().fetchAccounts();
      throw error;
    }
    console.info('[Codex Switch][Store] switchCodexAccount finished', {
      accountId,
      elapsedMs: Math.round(performance.now() - flowStartedAt),
    });

View on GitHub (pinned to 1ed8b77992)

Solutions

  1. Re-fetch the account list and pick a valid accountId before switching
  2. Handle CODEX_STALE_ACCOUNT by falling back to the store's current account
  3. Refresh the persisted current-account cache after deletions
  4. Avoid keeping account ids across sessions without revalidating them

Example fix

// before
await useCodexAccountStore.getState().switchAccount(deletedId);
// after
const { accounts, switchAccount } = useCodexAccountStore.getState();
if (accounts.some(a => a.id === deletedId)) {
  await switchAccount(deletedId);
} else {
  const current = await useCodexAccountStore.getState().fetchCurrentAccount();
}
Defensive patterns

Strategy: validation

Validate before calling

const exists = useCodexAccountStore.getState().accounts.some(a => a.id === accountId);
if (!exists) throw new Error(`account ${accountId} not found`);

Type guard

function accountExists(accounts: {id: string}[], accountId: string): boolean {
  return accounts.some(a => a.id === accountId);
}

Try / catch

try {
  await switchAccount(accountId);
} catch (e) {
  if ((e as Error).message === 'CODEX_STALE_ACCOUNT') {
    await fetchCurrentAccount(); // fall back to current account
  }
}

Prevention

When it happens

Trigger: Calling switchAccount(accountId, ...) with an id not present in get().accounts (stale id after account deletion, concurrent fetchAccounts removal, or persisted cache pointing at a removed account).

Common situations: Two windows/processes managing accounts simultaneously; account deleted elsewhere while UI still holds its id; restoring from an outdated persisted current-account cache.


AI-assisted analysis of jlcodes99/cockpit-tools@1ed8b77992 (2026-09-05). Data as JSON: /api/errors/e37b763151625e9c. Report an issue: GitHub.