Implementation Details
June 1, 2026 ยท View on GitHub
This document is the implementation index for codex-auth. Command-specific behavior lives in docs/commands/README.md.
Related Documents
- Command behavior: docs/commands/README.md
- API refresh and endpoint rules: docs/api.md
- File permissions: docs/permissions.md
- Schema migration: docs/schema-migration.md
- Test organization: docs/tests.md
- Release and CI: docs/release.md
Runtime State
codex-auth stores local state under the resolved Codex home. The resolution order is:
CODEX_HOMEwhen it is set to a non-empty existing directoryHOME/.codexUSERPROFILE/.codexon Windows
Managed files:
<codex_home>/auth.json<codex_home>/accounts/registry.json<codex_home>/accounts/<account file key>.auth.json<codex_home>/accounts/backup/<codex_home>/accounts/auth.json.bak.YYYYMMDD-hhmmss[.N]<codex_home>/accounts/registry.json.bak.YYYYMMDD-hhmmss[.N]<codex_home>/sessions/...
Registry Compatibility
registry.json.schema_versionis the on-disk migration gate.version = 2registries usingactive_emailand email-keyed snapshots are migrated to the current schema.- Current-layout files that still use the top-level
version = 3key are rewritten to the current schema. - Loading a supported older schema performs the migration in memory and rewrites
registry.jsonin the current format. - Loading a newer
schema_versionis rejected withUnsupportedRegistryVersion. - Saving always rewrites
registry.jsoninto the current schema field set.
See docs/schema-migration.md for versioning policy and migration rules.
Account Identity
codex-auth separates the user identity from the ChatGPT workspace/account context.
For ChatGPT OAuth auth:
chatgpt_account_idstores the ChatGPT account context used by local matching and ChatGPT API headers.- Account context selection is ordered:
- use non-empty
tokens.account_id - otherwise use non-empty JWT
https://api.openai.com/auth.chatgpt_account_id - otherwise use JWT
https://api.openai.com/auth.organizations[].id
- use non-empty
organizations[].idis only a fallback for auth files that omit the legacy account id fields. When it is used,chatgpt_account_idis anorg-...workspace identifier rather than the legacy UUID account id.- Organization fallback chooses the organization with
is_default = true; if none exists, it uses the first organization with a non-emptyid. chatgpt_user_idis read from JWT auth claims, falling back touser_id.- The local unique key is
record_key = chatgpt_user_id + "::" + chatgpt_account_id; the second segment may be either a legacy account id or the organization fallback id. account_keystores this localrecord_key.- Snapshot filenames are derived from
record_key; filename-unsafe values are base64url-encoded. - Email is normalized to lowercase and used for display/grouping, not identity.
For OpenAI API-key auth:
OPENAI_API_KEYis read fromauth.json.- The key is verified with
GET https://api.openai.com/v1/me. emailfrom/v1/meis normalized to lowercase and used for display/grouping.- The local unique key is
account_key = "apikey::" + me.id + "::" + sha256(OPENAI_API_KEY). - The raw API key is stored only in the managed auth snapshot, never in
registry.json, snapshot filenames, or display labels. - API-key rows use a local
API key <fingerprint>account label so multiple keys under the same email remain distinguishable.
Auth Parsing
If OPENAI_API_KEY is present, the account is treated as API-key auth. Otherwise, ChatGPT auth requires:
tokens.access_tokentokens.id_token- a ChatGPT account context from
tokens.account_id, JWThttps://api.openai.com/auth.chatgpt_account_id, or JWThttps://api.openai.com/auth.organizations[].id - JWT user identity from
chatgpt_user_idoruser_id
If required identity fields are missing or mismatched, import/login fails. Existing-registry foreground sync skips unsyncable auth files and continues with registry state already on disk.
Active Auth Sync
Foreground account commands sync auth.json before their main work when the current auth file is parseable.
The sync flow is:
- Read
~/.codex/auth.json. - Parse email, plan, auth mode,
chatgpt_user_id, andchatgpt_account_id. - Match by
record_key. - Update the matching account and active key, or create a new account record when no match exists.
- Rewrite the managed account snapshot when contents changed.
The empty-registry auto-import path still requires a parseable auth file. Once a registry exists, malformed or incomplete auth.json is skipped rather than deleting stored accounts.
Backups and Cleanup
auth.jsonbackups are created only when contents change.registry.jsonbackups are created only when contents change.- Backups are stored under
~/.codex/accounts/with local-time names. - Same-second collisions get a
.Nsuffix. - The newest five managed backups are retained.
codex-auth cleanis whitelist-based for the current schema and affects only~/.codex/accounts/.
Command behavior for cleanup lives in docs/commands/clean.md.
Local Usage Data
When API usage refresh is disabled, local usage refresh reads Codex rollout files under ~/.codex/sessions/**/rollout-*.jsonl.
- The newest rollout file by
mtimeis scanned. - The scanner looks for
type:"event_msg"andpayload.type:"token_count". - The last parseable
rate_limitsobject in that file is used. - Rollout events older than the current account activation time are ignored.
- Each account stores its own last consumed rollout signature.
- Rate limits map
window_minutes = 300to 5h andwindow_minutes = 10080to weekly. - Past reset timestamps render as
100%.
Display Model
- Human-readable account displays group records by email.
- Group headers are not selectable; child account rows are selectable.
- Alias labels take precedence for child rows.
- Duplicate workspace-style plan labels may use stable numbered labels.
PLANcomes from the auth claim when available, then falls back to usage snapshot plan data.- Usage cells show remaining percentage plus reset time when reset data is known.
LAST ACTIVITYis rendered as relative time fromlast_usage_at.
Command-specific display behavior lives in the relevant file under docs/commands/.