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
- Re-fetch the account list and pick a valid accountId before switching
- Handle CODEX_STALE_ACCOUNT by falling back to the store's current account
- Refresh the persisted current-account cache after deletions
- 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
- Revalidate ids against a fresh account list before switching
- Invalidate persisted current-account caches after deletions
- Listen to store changes to drop references to removed accounts
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.