{"record":{"id":"e37b763151625e9c","repo":"jlcodes99/cockpit-tools","slug":"codex-stale-account","errorCode":"CODEX_STALE_ACCOUNT","errorMessage":"CODEX_STALE_ACCOUNT","messagePattern":"CODEX_STALE_ACCOUNT","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/stores/useCodexAccountStore.ts","lineNumber":261,"sourceCode":"      accountId,\n    });\n    const accounts = await codexService.listCodexAccounts();\n    console.info('[Codex Switch][Store] listCodexAccounts finished', {\n      accountId,\n      elapsedMs: Math.round(performance.now() - flowStartedAt),\n    });\n    // Drop any in-flight fetch results before applying mutation state.\n    invalidateCodexFetchRequests();\n    set({ accounts, accountsLoaded: true, loading: false, error: null });\n    persistCodexAccountsCache(accounts);\n\n    const targetExists = accounts.some((account) => account.id === accountId);\n    if (!targetExists) {\n      const currentAccount = await codexService.getCurrentCodexAccount();\n      invalidateCodexFetchRequests();\n      set({ currentAccount });\n      persistCodexCurrentAccountCache(currentAccount);\n      throw new Error(CODEX_STALE_ACCOUNT_ERROR);\n    }\n\n    let account: CodexAccount;\n    try {\n      account = await codexService.switchCodexAccount(accountId, {\n        reauthTokenGeneration: options?.reauthTokenGeneration,\n        launchAfterSwitch: options?.launchAfterSwitch,\n      });\n    } catch (error) {\n      // Token Authority 可能已把账号标记为 requires_reauth。立即回读账号库，\n      // 让账号卡片和切号弹框都展示最新的 API-only / 需授权状态。\n      void get().fetchAccounts();\n      throw error;\n    }\n    console.info('[Codex Switch][Store] switchCodexAccount finished', {\n      accountId,\n      elapsedMs: Math.round(performance.now() - flowStartedAt),\n    });","sourceCodeStart":243,"sourceCodeEnd":279,"githubUrl":"https://github.com/jlcodes99/cockpit-tools/blob/1ed8b77992d62ca81fabf744deb0839ad361d5bf/src/stores/useCodexAccountStore.ts#L243-L279","documentation":"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.","triggerScenarios":"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).","commonSituations":"Two windows/processes managing accounts simultaneously; account deleted elsewhere while UI still holds its id; restoring from an outdated persisted current-account cache.","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"],"exampleFix":"// before\nawait useCodexAccountStore.getState().switchAccount(deletedId);\n// after\nconst { accounts, switchAccount } = useCodexAccountStore.getState();\nif (accounts.some(a => a.id === deletedId)) {\n  await switchAccount(deletedId);\n} else {\n  const current = await useCodexAccountStore.getState().fetchCurrentAccount();\n}","handlingStrategy":"validation","validationCode":"const exists = useCodexAccountStore.getState().accounts.some(a => a.id === accountId);\nif (!exists) throw new Error(`account ${accountId} not found`);","typeGuard":"function accountExists(accounts: {id: string}[], accountId: string): boolean {\n  return accounts.some(a => a.id === accountId);\n}","tryCatchPattern":"try {\n  await switchAccount(accountId);\n} catch (e) {\n  if ((e as Error).message === 'CODEX_STALE_ACCOUNT') {\n    await fetchCurrentAccount(); // fall back to current account\n  }\n}","preventionTips":["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"],"tags":["stale-state","account-not-found","concurrency"],"backgroundTag":"stale-reference","analyzedSha":"1ed8b77992d62ca81fabf744deb0839ad361d5bf","analyzedAt":"2026-09-05T09:51:41.178Z","contentChangedAt":"2026-09-05T09:51:41.178Z","schemaVersion":2},"datasetVersion":"2026-09-12T12:17:11.808Z"}