-
Notifications
You must be signed in to change notification settings - Fork 1.3k
fix(codex): reset main runtime state after account switch #370
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
2245961
ed7147f
94e40ad
7432203
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,15 +1,46 @@ | ||
| import { removeCodexAccountCredential } from "./account-store"; | ||
| import { clearAccountNeedsReauth } from "./account-runtime-state"; | ||
| import { getMainChatgptAccountId } from "./auth-collision"; | ||
| import { MAIN_CODEX_ACCOUNT_ID, setMainAccountPlan } from "./main-account"; | ||
| import { clearAccountQuota } from "./quota"; | ||
| import { clearCodexUpstreamHealthForAccount, clearThreadAccountMapForAccount } from "./routing"; | ||
| import { invalidateCodexWebSocketsForAccount } from "./websocket-registry"; | ||
| import { clearMainAccountInfoCache } from "./main-account-cache"; | ||
| import type { OcxConfig } from "../types"; | ||
|
|
||
| let observedMainChatgptAccountId: string | undefined; | ||
|
|
||
| export function purgeCodexAccountRuntimeState(accountId: string): void { | ||
| clearAccountNeedsReauth(accountId); | ||
| clearAccountQuota(accountId); | ||
| clearThreadAccountMapForAccount(accountId); | ||
| clearCodexUpstreamHealthForAccount(accountId); | ||
| if (accountId === MAIN_CODEX_ACCOUNT_ID) clearMainAccountInfoCache(); | ||
| } | ||
|
|
||
| /** | ||
| * The main Codex login is stored under the stable `__main__` alias, while | ||
| * `~/.codex/auth.json` can be replaced with credentials for another physical | ||
| * ChatGPT account. Drop alias-keyed runtime state when that identity changes so | ||
| * cooldown, quota, reauth, and thread affinity do not leak across accounts. | ||
| */ | ||
| export function reconcileMainCodexAccountRuntimeState(): boolean { | ||
| const currentAccountId = getMainChatgptAccountId(); | ||
| // A missing/malformed auth.json is an unknown identity, not a confirmed account switch. Keep the | ||
| // prior observation and its safety state until a real account id can be read again. | ||
| if (currentAccountId === null) return false; | ||
| const previousAccountId = observedMainChatgptAccountId; | ||
| observedMainChatgptAccountId = currentAccountId; | ||
| if (previousAccountId === undefined || previousAccountId === currentAccountId) return false; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When the startup quota prime or the accounts API calls Useful? React with 👍 / 👎. |
||
|
|
||
| purgeCodexAccountRuntimeState(MAIN_CODEX_ACCOUNT_ID); | ||
| setMainAccountPlan(null); | ||
|
Comment on lines
+36
to
+37
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
This reset path clears the shared quota store and plan, but it does not invalidate Useful? React with 👍 / 👎. |
||
| invalidateCodexWebSocketsForAccount(MAIN_CODEX_ACCOUNT_ID); | ||
| return true; | ||
| } | ||
|
|
||
| export function resetMainCodexAccountIdentityTrackingForTests(): void { | ||
| observedMainChatgptAccountId = undefined; | ||
| } | ||
|
|
||
| export function deleteCodexAccount(runtimeConfig: OcxConfig, accountId: string): void { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| import type { StoredAccountQuota } from "./quota"; | ||
|
|
||
| export interface MainAccountInfo { | ||
| email: string | null; | ||
| plan: string | null; | ||
| quota: Omit<StoredAccountQuota, "updatedAt"> | null; | ||
| } | ||
|
|
||
| export interface CachedMainAccountInfo extends MainAccountInfo { | ||
| ts: number; | ||
| } | ||
|
|
||
| let cachedMainAccountInfo: CachedMainAccountInfo | null = null; | ||
|
|
||
| export function getMainAccountInfoCache(): CachedMainAccountInfo | null { | ||
| return cachedMainAccountInfo; | ||
| } | ||
|
|
||
| export function setMainAccountInfoCache(value: CachedMainAccountInfo): void { | ||
| cachedMainAccountInfo = value; | ||
| } | ||
|
|
||
| export function clearMainAccountInfoCache(): void { | ||
| cachedMainAccountInfo = null; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If
auth.jsonis temporarily absent or malformed during a Codex logout/login or non-atomic rewrite,getMainChatgptAccountId()returnsnull; this code records thatnullas the observed identity and treats bothA -> nullandnull -> Aas account changes. That purges__main__cooldown/reauth/quota state for the same physical account, so a 429-cooled main account can be retried upstream immediately after a transient file-read gap. Keep the last non-null observed account (or only purge when both previous and current identities are non-null and different) so missing credentials fail closed without resetting per-account health.Useful? React with 👍 / 👎.