codex-switch is a Node.js/TypeScript CLI for managing multiple isolated Codex login profiles.
It keeps each profile in its own CODEX_HOME, stores sensitive auth.json data in the OS credential store, and lets you switch manually between profiles without overwriting one shared Codex login state.
The repository also ships a Windows-only tray app that uses the CLI as its backend. The tray app gives you a global active-profile switcher for future Codex launches, shows best-effort remaining usage and reset times, and can automatically discover a new raw codex login from the default ~/.codex/auth.json.
See docs/login-flow.md for a sanitized write-up of the observed Codex ChatGPT login flow and workspace-selection findings.
- Multiple isolated Codex profiles
import-currentto capture an existing Codex loginloginwith optional auto-naming fromemail__chatgpt_account_id- Windows-default isolated browser login, with
--native-browseras an explicit fallback - Same-account dedupe based on
chatgpt_account_id authFingerprinttracking so external logins and token refreshes can be synchronized safely- Best-effort workspace title detection from ChatGPT JWT claims
sync-currentto import or switch to the current raw Codex login--jsonoutput for tray/frontend integrationsrunpassthrough so Codex launches under the selected profiledesktop launchto start the official Codex desktop app under a managed session on Windowsdesktop statusto inspect the managed desktop sync statedesktop switchto restart the managed Codex desktop app on another saved profile- Optional shell hook so
codexcan transparently route throughcodex-switch - Windows tray app for one-click manual profile switching
- Node.js 20+
- A working
codexCLI inPATH, orCODEX_SWITCH_CODEX_COMMANDconfigured - OS credential store support compatible with
keytar
npm install
npm run buildFor local CLI usage:
npm linkFor the Windows tray app, codex-switch.cmd should be available in PATH. If you prefer not to npm link, set CODEX_SWITCH_TRAY_COMMAND to the command or full path you want the tray app to launch.
Personal desktop settings such as proxy information, the Codex Desktop path, and the preferred working directory live in a local JSON config file:
- default path:
~/.codex-switch/config.json - if
CODEX_SWITCH_HOMEis set:<CODEX_SWITCH_HOME>/config.json
Example:
{
"codex": {
"command": "codex",
"commandArgs": []
},
"desktop": {
"proxyUrl": "http://127.0.0.1:7890",
"clientPath": "",
"workingDirectory": "D:\\001_CODEX",
"clientArgs": [],
"monitorPollIntervalMs": 60000
}
}Notes:
- this file is machine-local and should not be committed
- matching environment variables still override config values
monitorPollIntervalMscontrols how often the managed desktop monitor checks for workspace/profile changes
If you want the shortest path on Windows:
npm install
npm run build
npm link
codex-switch import-current
codex-switch list
codex-switch use "<your-profile>"
Invoke-Expression (& codex-switch shell init pwsh | Out-String)
codexWhat each step does:
npm link: makescodex-switchavailable as a global command so the tray app and your shell can find itimport-current: captures the login currently stored in the default~/.codexlist: shows the profiles currently managed bycodex-switchuse: marks one profile as the active profileshell init pwsh: teaches the current PowerShell session to routecodexthroughcodex-switch runcodex: after the shell hook is loaded, this starts Codex under the active managed profile
If you want the shell hook every time PowerShell starts, add it to $PROFILE:
if (!(Test-Path $PROFILE)) { New-Item -ItemType File -Path $PROFILE -Force }
Add-Content $PROFILE 'Invoke-Expression (& codex-switch shell init pwsh | Out-String)'Import the currently logged-in Codex state:
codex-switch import-currentCreate a new profile through the official login flow:
codex-switch login
codex-switch login team-a
codex-switch login --isolated-browser
codex-switch login --native-browserOn Windows, plain codex-switch login now defaults to the isolated-browser flow. codex-switch starts the official codex app-server account/login/start flow inside the managed profile, opens the returned ChatGPT auth URL in an isolated Chrome/Edge user-data directory, and waits for the official account/login/completed notification before using the resulting auth.json. This keeps the upstream login context intact while still reducing broken session state from the everyday browser profile.
Use --native-browser if you explicitly want the old behavior and let the upstream codex login command control both the browser launch and token exchange. Use --isolated-browser if you want to force the isolated app-server-backed flow explicitly in scripts or cross-check behavior.
View and switch profiles:
codex-switch list
codex-switch status --all
codex-switch use team-a
codex-switch sync-currentRun Codex under the active profile:
codex-switch run
codex-switch run -- --versionInitialize a shell hook:
codex-switch shell init pwsh
codex-switch shell init bash
codex-switch shell init zshMachine-readable output is available for integrations:
codex-switch list --json
codex-switch status --all --json
codex-switch use team-a --json
codex-switch sync-current --json
codex-switch desktop status --json
codex-switch desktop switch team-a --jsonOn Windows, codex-switch can also launch the official Codex desktop app through a managed session:
codex-switch desktop launch
codex-switch desktop status
codex-switch desktop switch team-aThis mode is the supported way to keep codex-switch and the official Codex desktop app aligned:
codex-switchseeds the desktop session from the current active managed profile- the desktop app runs with a dedicated managed
CODEX_HOME - a background monitor watches that managed desktop session for auth changes
- when the desktop app switches to another workspace,
codex-switchimports or reuses the matchingchatgptAccountIdprofile and marks it active - the managed desktop monitor checks for changes every 60 seconds by default
desktop switch <profile>restarts the managed desktop app onto another saved profile
Current v1 boundary:
- automatic desktop sync is only guaranteed for Codex desktop instances launched through
codex-switch desktop launch - already-running desktop instances and VS Code / Cursor extensions are not auto-synchronized
- Run
npm linkonce socodex-switch.cmdis inPATH. - Start
windows-tray/publish/win-x64/CodexSwitch.Tray.exe. - In the tray menu, use
Launch Codex Desktop,Add Profile, orImport Current Login. - If managed desktop sync is running, clicking a profile restarts the desktop app on that profile. Otherwise it just updates the active profile for future launches.
- Launch future managed Codex sessions with
codex-switch run, or use the PowerShell shell hook so plaincodexfollows the active profile.
The tray app is in windows-tray/CodexSwitch.Tray. It does not replace every running Codex session in the system. Instead, it maintains one global active profile for the managed codex-switch flow, so the next terminal/Codex process you launch through that flow uses the selected profile.
The tray app:
- shows all managed profiles in the system tray
- displays best-effort workspace, remaining usage, and reset time
- lets you click a profile to make it active
- supports
Add ProfileandImport Current Login - can launch the official Codex desktop app through
Launch Codex Desktop - shows whether desktop auto-sync is connected
- restarts the managed desktop app onto a selected profile when you click a profile while desktop sync is running
- watches the default
~/.codex/auth.jsonand syncs newly detected external logins
Build and test it with:
npm run build:tray
npm run test:trayPublish a Windows binary with:
powershell -ExecutionPolicy Bypass -File scripts/publish-windows-tray.ps1codex-switch login [profile] [--isolated-browser|--native-browser]codex-switch import-current [profile]codex-switch listcodex-switch status [--all|--profile <name>]codex-switch sync-currentcodex-switch desktop launchcodex-switch desktop statuscodex-switch desktop switch <profile>codex-switch use <profile>codex-switch run [--profile <name>] -- <codex args...>codex-switch doctorcodex-switch shell init <pwsh|bash|zsh>
- Sensitive authentication material is stored in the system credential store, not committed to the repository.
- Managed profile homes keep non-sensitive Codex state and sanitized skeleton files only.
- Workspace detection is best-effort and intended for display, not as a stable unique identifier.
- The tray app and CLI switch a managed global active profile for future launches. Desktop auto-sync only applies to official Codex desktop instances launched through
codex-switch desktop launch; already-running desktop instances and the VS Code Codex extension are outside this v1 guarantee.
Why is the default profile name something like user@example.com__66414859-b7e5-42c4-a8f4-549b05779e09?
That is the current auto-generated naming rule for imports and logins without a manually supplied profile name:
- left side: the ChatGPT email found in the login token
- right side: the ChatGPT account/workspace identifier (
chatgpt_account_id)
This is intentionally stable and dedupe-friendly, because the right-hand identifier is what codex-switch uses to recognize “the same profile� across re-imports and logins.
If you want a friendlier label, pass one explicitly:
codex-switch import-current team-a
codex-switch login personalThe underlying profile still keeps the account/workspace metadata internally even if the display name is friendlier.
Yes. codex-switch dedupes by chatgpt_account_id, not just by email.
In real login captures, two different workspace selections under the same email account produced two different chatgpt_account_id values. That means the same ChatGPT email can still map to multiple managed profiles when different workspace/account contexts are selected during login.
This is also why organizations[].title is treated as best-effort display data only. In the observed login flow, that title was not always enough to distinguish the actual workspace context, while chatgpt_account_id was.
npm test
npm run check
npm run build
npm run test:tray
npm run build:trayMIT