Repository navigation
Feature Guide
codex-switch manages multiple file-backed Codex logins for CLI and desktop environments, observes their quota state, and selects an account for the next Codex process.
On macOS, a separate CLI installation is optional: when no codex is on PATH, the tool discovers ChatGPT/Codex engines in the user's and system Applications folders. CODEX_SWITCH_CODEX explicitly selects an executable on any platform and takes precedence over PATH. An invalid explicit path is an error. launch starts the selected engine in the terminal; it does not open or restart the desktop GUI. Account operations require file-backed OAuth credentials; keyring, auto and ephemeral stores remain unsupported. Reconnect existing desktop sessions after switching and confirm their active account.
Authentication prerequisite: Codex must use the file credential store. Set
cli_auth_credentials_store = "file"in$CODEX_HOME/config.toml. Explicitkeyring,auto, andephemeralstores are rejected because they can bypass theauth.jsonfile that codex-switch switches.
Add accounts with browser or device-code login:
codex-switch login work
codex-switch login --device serverExisting auth.json files can be imported individually or from a directory. Imports are validated in stages — JSON format, required token structure with a decodable id_token, then a live usage-service check — before being saved under collision-free aliases:
codex-switch import ~/auth-backupsInteractive login deduplicates local profiles by account_id first and falls back to email when safe. Import is deliberately create-only and never updates an existing profile: Usage API validation proves that the bearer can access a workspace, but a Team workspace ID can be shared by several users and cannot authorize overwriting another saved credential. For the same reason, import will not write a second profile for an account you already have: when the incoming file is byte-identical to a saved profile, or carries the same account_id and email, the import is skipped before validation so its single-use refresh token is never spent. Use login <alias> to refresh an existing profile.
export packs every saved profile, custom provider (API key and model catalog included) and config.toml into one passphrase-encrypted bundle; import restores it on the other machine:
# machine A
codex-switch export ~/codex-switch.csbundle
# machine B
codex-switch import ~/codex-switch.csbundle --dry-run
codex-switch import ~/codex-switch.csbundle --strategy merge --yes --activateThe bundle is AES-256-GCM encrypted under an Argon2id key and written with mode 0600. A wrong passphrase, a modified file and a bundle from a newer codex-switch fail with distinct errors before anything is written. Import is offline — tokens are not sent to the Usage API — but the managed workspace policy and identity checks still run, and one invalid entry aborts the whole import.
Conflicts follow --strategy: skip (default) keeps the local copy, overwrite replaces it after a backup to import-backups/, and merge keeps the credential with the newer last_refresh and adds only missing config.toml keys (providers are kept as with skip). A different account under a taken alias is always imported under a new alias, never over the other account. The dry-run flags [proxy] as machine-specific and lists each provider's base_url and codex_config override count. The current account is switched only with --activate.
The live ~/.codex/auth.json is treated like a profile: when the import updates your current profile, live is replaced only if it is the same account and provably older than the imported credentials. If Codex refreshed it more recently, merge keeps everything and overwrite asks for --replace-live; a live login of another account is never touched. Unencrypted bundles carry no authentication, so importing one needs confirmation or --yes, as does a provider that adds Codex overrides. If an import is killed halfway, the next import prints which backup restores which file. An import that leaves any item skipped or ambiguous exits 1 (--json: ok:false, code nothing_imported or partial_import) and tells you to keep the bundle; --activate never switches over a newer live login of the same account.
Refresh tokens are single-use. Export does not retire the accounts on the source machine; if both machines keep using the same account, one of them will fail with refresh_token_reused and need codex-switch login <alias>.
Profile deletion is recoverable. An inactive profile is moved under deleted-profiles/ after confirmation; the active profile cannot be deleted. See recovery instructions.
Interactive commands compare the live $CODEX_HOME/auth.json against saved profiles before doing their own work:
- A new account (for example after a plain
codex login) triggers an offer to save it as a profile. - A refreshed token for a known account triggers an offer to update that profile.
- Non-interactive runs (pipes, cron, CI) report the change but never modify state silently.
Use the CLI for scripts and quick inspection, or the TUI for an interactive dashboard:
codex-switch list
codex-switch --json list
codex-switch tuiThe usage model includes the main 5-hour and 7-day windows, additional model-specific pools, reset cards, spend limits, account restrictions, and model capabilities returned by the authenticated service. Cached entries are scoped by profile alias and retain their own fetch time.
Normal reads refresh only stale entries. Use list -f or the TUI refresh action when a fresh network read is required.
The TUI has four tabs: Accounts (ChatGPT OAuth, quota, scoring), Providers (Beta) (custom API endpoints), Settings (config.toml), and Logs (bounded INFO session diagnostics). Tab / Shift+Tab cycles them. Mouse users can click tabs, Accounts/Providers rows, Settings fields, provider add/edit form fields, launch-picker rows, confirmation y/n, and the visible footer actions. Double-click a row to open its account or provider launch menu; double-click a launch-picker model to start Codex. Use the wheel in Logs, Help, menus, Settings, and modal lists. Click outside a dismissible popup to close it; modal forms do not click through to the page behind them. q and h apply on the main view; forms, text edits, menus, and confirmations keep their own Esc and confirmation rules. o launches Codex on Accounts and Providers. On Accounts, u (when no accounts are marked) switches the selected account and shows Switching to … progress, t toggles session-only auto-refresh, and the account menu's w performs a one-time warmup. Settings uses j/k for fields and s to save; s on Accounts still cycles sort. Unsaved Settings edits survive leaving the tab and require confirmation before quitting; while a field is being edited, Tab stays on Settings.
The TUI account detail page is a single scrollable column with identity and organization labels, token expiry times in the local timezone, every quota pool with a pace marker, available reset cards, and the models the account may use. Model names and reasoning-effort capabilities are discovered from the authenticated service at runtime, not hardcoded. The full shortcut list is in the command reference and under h inside the TUI.
Quota refresh also reads each account's model access programs and subscription expiry. If any account advertises Daybreak Blue or Red, the account table shows a Daybreak column with independent blue/red sun symbols; ? means unknown and -- means the catalog explicitly advertises no Daybreak access. The selected-account panel and account menu omit the Daybreak row when the account is confirmed to have neither variant; unknown status and query failures remain visible. These indicate catalog eligibility, not whether Daybreak is enabled. Subscription expiry is shown in details using the local timezone. Live subscription data takes precedence over a labeled JWT snapshot with its original check time; an expiry date does not promise automatic renewal. Metadata failures remain visible without blocking quota display.
Select an explicit profile:
codex-switch use workOr let the adaptive selector rank all profiles:
codex-switch useSelection has two phases:
- Eligibility excludes candidates with exhausted 5h or 7d windows, critically low weekly headroom with a distant reset, or an unsafe Free-plan balance.
- Scoring ranks the eligible candidates by tier preference (Team accounts get priority by default), pace-aware 5h headroom, weekly sustainability, quota that is close to resetting, and recent use.
If every account is ineligible, the best fallback is reported instead of pretending an account is healthy.
Switching replaces the live $CODEX_HOME/auth.json atomically while holding a process lock. Codex 0.157 and newer attaches interactive sessions to a shared local app-server daemon that loads auth.json once, so when a switch changes the file codex-switch restarts a running daemon (codex app-server daemon restart): new sessions and sessions that reconnect use the selected account, and a turn that was in progress is interrupted. Auto-select, TUI u, and login paths that activate credentials behave the same way. The file is compared as canonical JSON, so a formatting-only rewrite (or re-selecting the account that is already live) never restarts the daemon. Each daemon call is bounded to 15 seconds, and a failed restart is a warning rather than a failed switch. A stopped daemon is left alone, and an older Codex without the daemon reads the file at its next start. To keep switches from interrupting attached sessions, set [use] restart_app_server = false or toggle it in the TUI Settings tab; the switch then prints the manual codex app-server daemon restart command.
On Windows the private-write path reads a directory's protected ACL first and rewrites it only when it is not already exactly that ACL, so a switch no longer re-propagates permissions through a large $CODEX_HOME tree.
launch selects or stages a profile, starts Codex, then restores the previous live authentication after the configured compatibility delay:
In the TUI Accounts page or account menu, o opens the same launch picker used by providers. (Codex default) adds no model or reasoning override; cached account models can supply a one-shot model and reasoning effort, and the picker also accepts extra Codex arguments.
codex-switch launch work -- --model gpt-5.4
codex-switch launch work -- exec --json "review this"
codex-switch launch -- exec --json "do the thing"
codex-switch launch -- -s workspace-write -a neverArguments after -- are Codex's, not codex-switch's. A known Codex subcommand (exec, resume, …) can start the argv without -- (codex-switch launch exec --json "…"). Tokens on both sides of -- are kept. The separator is still required when the Codex argv starts with a prompt that looks like an alias, or a flag that also exists on codex-switch (--json, --color, --model) immediately after the alias. Current Codex has no --full-auto; use -a never, --sandbox, or --dangerously-bypass-approvals-and-sandbox. --json launch prints one JSON object after Codex exits and captures at most 1 MiB from each Codex output stream; the corresponding *_truncated fields report clipping.
The launch lock serializes overlapping launch sessions. The restore delay is configurable (launch.restore_delay_secs) because Codex does not expose an authentication-read handshake. When the installed Codex lists --no-daemon (0.156 and newer), a ChatGPT launch adds it so the session reads the staged file instead of joining the shared app-server daemon, which would keep the account it already holds; an argv that already contains --no-daemon, --remote, or agents is passed through unchanged. The codex --help probe that decides this is bounded to 10 seconds; if routing cannot be determined, launch refuses before staging any credentials rather than risk running the session on the wrong account.
Beta: a provider profile saves a Responses-compatible third-party API endpoint and bearer key under $CODEX_SWITCH_HOME/providers/ rather than as a ChatGPT auth.json. Configure multiple models with per-model reasoning and web_search, fetch the gateway's model catalog, and launch Codex. Compatibility depends on the gateway and selected model. Providers do not show ChatGPT quota or participate in ChatGPT automatic account selection. Typical case: OpenRouter.
codex-switch provider add openrouter \
--base-url https://openrouter.ai/api/v1 \
--model openai/gpt-5.3-codex
codex-switch launch openrouterlaunch <provider> does not swap $CODEX_HOME/auth.json. It starts Codex with a native per-run profile in the shared Codex home, injects the key into the child environment, and keeps launch metadata/catalogs under provider-runs/<identity_id>/<run_id>. MCP servers, skills, plugins, hooks and session storage remain shared; concurrent launches have separate model/provider configuration. Provider resume resolves IDs, unique names, --last, and the bare picker within the selected provider identity and passes the exact session ID to Codex. Auto-select (launch with no alias) and use remain ChatGPT-only. Launch performs no gateway request unless a saved probe marked the chosen model unsupported. codex-switch provider probe <alias> explicitly checks POST /responses; verdicts are kept for 7 days, and only a live re-probe at launch that still reports the model unsupported can block it (a supported answer proceeds and refreshes the record; an inconclusive answer or network failure proceeds with a stderr warning and drops the record).
A provider holds several models; reasoning effort and web_search are per model. In the TUI Providers tab, Enter / o opens a picker for a saved model, a one-shot reasoning override, and optional extra Codex argv; e edits the provider (including env key, wire API, and extra -c). The API key is read from a hidden prompt (or --api-key-stdin), never from argv. Full workflow, DeepSeek-via-OpenRouter, model-specific settings, TUI add/edit/rename, and the security contract are in Custom API providers.
When the whole candidate pool is exhausted, an interactive use or launch can offer to consume the earliest-expiring reset card. Automation must opt in explicitly:
codex-switch use --consume-card
codex-switch reset-card work --yesJSON or non-interactive execution never consumes a card without the explicit flag.
Fresh paid accounts show no reset timer until their first real request. warmup sends a one-time minimal request to activate inactive 5h main and model-specific quota windows discovered from the official model response:
codex-switch warmup
codex-switch warmup workModel names are discovered at runtime rather than maintained as a hardcoded compatibility list. Warmup prefers an available Luna model for its main request, falls back to mini for older catalogs, then to the model with the highest API priority. If the backend rejects the chosen model with HTTP 400 "not supported", the single retry refreshes the model list and excludes the rejected model. It reads the full response stream and requires a completed response event (response.completed); HTTP 200 alone does not count as success. A transient failure of the proactive token refresh before warmup (for example a network error) does not use up the one recovery refresh, so a later 401 can still be recovered; a permanent rejection such as refresh_token_invalidated stops without replaying the credential. A tiny completed request may still display 0% usage. A fresh usage sample showing that its 5h reset timer has been running for at least five minutes counts as an active window. Accounts with only a 7-day window (free plans), already-active 5h pools, and unavailable pools are skipped. In the TUI, use the selected account menu's w for a one-time warmup; the batch menu's w warms marked accounts that have a 5h window. The old W automatic-warmup shortcut and daemon schedule are removed. Press t on the Accounts page to toggle session-only automatic usage refresh.
The binary no longer installs or owns a resident daemon, service, timer, or automatic account switcher. If periodic work is useful, install a user-level OS task yourself and invoke the one-time CLI operations. list --force --json refreshes usage, warmup activates quota windows, use without an alias selects and switches to the best eligible ChatGPT profile, and launch without an alias starts Codex with the best profile. Schedule use only when unattended credential changes are intended; a manual use affects the next Codex process and restarts a running Codex app-server daemon when the live file changed (re-selecting the profile that is already live does not), while an already-running codex exec or codex --no-daemon process must be restarted.
The examples below schedule a one-time warmup every 30 minutes. These tasks run as your user; declare CODEX_HOME or CODEX_SWITCH_HOME in the task environment when you rely on non-default paths. Use list --force --json in the same task shape when you need a usage refresh instead.
Windows Task Scheduler (PowerShell):
$codex = Join-Path $env:LOCALAPPDATA 'Programs\codex-switch\codex-switch.exe'
$task = '"{0}" warmup' -f $codex
schtasks.exe /Create /TN 'codex-switch-warmup' /SC MINUTE /MO 30 /TR $task /FLinux cron: edit crontab -e and add:
*/30 * * * * /home/you/.local/bin/codex-switch warmupLinux systemd user timer: create ~/.config/systemd/user/codex-switch-warmup.service:
[Unit]
Description=Warm quota windows with codex-switch
[Service]
Type=oneshot
ExecStart=%h/.local/bin/codex-switch warmupCreate ~/.config/systemd/user/codex-switch-warmup.timer:
[Unit]
Description=Run codex-switch warmup
[Timer]
OnBootSec=5min
OnUnitActiveSec=30min
[Install]
WantedBy=timers.targetEnable it with systemctl --user daemon-reload and systemctl --user enable --now codex-switch-warmup.timer.
macOS launchd: save this as ~/Library/LaunchAgents/com.example.codex-switch-warmup.plist, replacing the executable path:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0"><dict>
<key>Label</key><string>com.example.codex-switch-warmup</string>
<key>ProgramArguments</key><array>
<string>/Users/you/.local/bin/codex-switch</string>
<string>warmup</string>
</array>
<key>StartInterval</key><integer>1800</integer>
</dict></plist>Load it with launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/com.example.codex-switch-warmup.plist; remove it with launchctl bootout gui/$(id -u)/com.example.codex-switch-warmup. Syntax references: Microsoft schtasks, systemd timers, cron, and Apple launchd.
Direct installs support the stable and rolling development channels and verify release checksums before replacing the binary. See Updating for channels, Homebrew rules, and legacy-install migration, and Testing development releases for the dev channel.
codex-switch self-update --check
codex-switch self-updateMost non-interactive commands support --json or --json-pretty. Structured output stays on stdout; progress and diagnostic messages use stderr. Commands that can consume a reset card or delete a profile require explicit non-interactive confirmation.
Never publish profile files, auth.json, provider API keys, unredacted debug output, proxy credentials, account IDs, email addresses, or workspace names.
- Need an exact command, flag, or TUI shortcut? Open the Command reference.
- Launching Codex against OpenRouter or another custom API? Open Custom API providers.
- Tune paths, proxy, cache, selection, and launch behavior in Configuration.
- Something failed? Start with Troubleshooting.