2120 字
11 分钟
codex-switch — Codex 多账号管理工具

那天我发现 Codex CLI 只能通过 OAuth 浏览器流程登录,而且一台机器只能有一个活跃凭据。Work 和 Personal 两个 OpenAI 账号,切来切去,还得保持跨 Mac 和 MBP 的同步。完全不优雅。

决定写个工具搞定这事。

问题背景#

Codex CLI 认证的硬约束

  • 仅支持 OAuth 浏览器登录(codex login
  • 凭据全局唯一:~/.codex/auth.json
  • 没有内置的多账号管理
  • 没有自动 token 刷新机制(token 有 7-14 天生命周期)

需求

  1. 快速切换多个 OpenAI 账号(rent / work 等)
  2. 自动刷新 token,无感知
  3. 跨机器同步(本地 Mac + 远程 MBP)
  4. 快照备份,30 天自动清理

解决方案架构#

核心设计#

~/.codex/auth.json (Codex 读取的活跃凭据)
↑↓
~/.codex/auth_<name>.json (命名账号快照,source of truth)
↑↓
~/.codex/.active_account (当前激活的别名)
~/.codex/journal/ (每次修改自动快照,30 天清理)
mbp:~/.codex/ (通过 scp 同步的远程副本)

关键点:

  • auth.jsonauth_<name>.json 始终保持一致(switch / refresh 时同步)
  • .active_account 记录当前别名,支持 refresh 时自动找到对应账号
  • journal 作为审计日志,可以回溯之前的任何 token 状态

Token 刷新机制#

OpenAI OAuth 实现了 refresh token rotation:每次刷新后,旧 token 作废,新 token 写入。

脚本调用 https://auth.openai.com/oauth/token 端点:

Terminal window
curl -X POST https://auth.openai.com/oauth/token \
-H "Content-Type: application/json" \
-d "{
\"grant_type\":\"refresh_token\",
\"client_id\":\"app_EMoamEEZ73f0CkXaXp7hrann\",
\"refresh_token\":\"rt_bIdf6WifVig4...\"
}"

返回新 access_tokenid_token、可能的新 refresh_token

并发安全:因为 refresh token 单次使用,多个进程同时刷新会互相冲突。解决办法是在主机级别加锁,或者干脆不允许并发——脚本采用后者(实际场景下用户不会同时在两台机器上跑 codex)。

核心命令#

codex-switch#

Terminal window
# 列出所有账号
codex-switch
# 输出:
# 账号列表:
# rent account_id=a3194... expires 2026-04-12 (+6d) last_refresh=2026-04-02 19:24 ◀ 当前
# work account_id=b5271... expires 2026-04-10 (+4d) last_refresh=2026-03-30 10:15
#
# 活跃 auth.json:
# rent account_id=a3194... expires 2026-04-12 (+6d) last_refresh=2026-04-02 19:24
#
# Journal 快照 (最近10条):
# auth_rent.20260406T174402Z.json
# auth_work.20260406T143015Z.json
# ... (自动清理 30 天前的)
# 创建新命名账号(从当前 auth.json)
codex-switch create work -f # -f 强制覆盖
# 立即切换
codex-switch work
# → 自动刷新 token
# → 问:同步到 mbp? [y/N]
# 新账号登录(清空凭据 → codex login → 保存 → 恢复原账号)
codex-switch login personal
# → 暂存当前凭据
# → 触发 `codex login`(浏览器弹出)
# → 登录新账号后,保存到 auth_personal.json
# → 恢复原活跃凭据
# → 问:同步到 mbp?
# → 问:立即切换到 personal?

codex-refresh#

Terminal window
# 刷新当前账号(读 .active_account)
codex-refresh
# → 调 OpenAI OAuth 刷新
# → auth.json + auth_<name>.json 双写
# → 问:同步到 mbp?
# 指定账号刷新
codex-refresh --account work
# 跳过询问,直接同步
codex-refresh --sync

实现细节#

参数流转和同步逻辑#

switch 的关键步骤(以 codex-switch work 为例):

Terminal window
# 1. Journal 快照当前 auth.json
journal_snapshot "~/.codex/auth.json"
journal_gc # 清理 30+ 天的
# 2. 复制新账号到活跃位置
cp ~/.codex/auth_work.json ~/.codex/auth.json
# 3. 记录当前别名
echo "work" > ~/.codex/.active_account
# 4. 自动刷新(不再单独问 sync,交给 refresh 统一处理)
codex-refresh --account work [--sync|--no-sync]

refresh 的 token 同步

如果指定了 account,刷新后会同时更新两个文件:

# 写入新 token
auth['tokens']['access_token'] = response['access_token']
auth['tokens']['refresh_token'] = response['refresh_token']
json.dump(auth, open(auth_file, 'w'))
# 同步命名文件(保持一致)
if account:
cp ~/.codex/auth.json ~/.codex/auth_account.json

跨机器同步

Terminal window
if do_sync:
for host in mbp:
scp ~/.codex/auth.json ${host}:.codex/auth.json
if account:
scp ~/.codex/auth_account.json ${host}:.codex/auth_account.json

关键:两个文件都推过去,mbp 上的 auth.jsonauth_account.json 状态与本地一致。

为什么需要 codex-journal#

Token 有生命周期,refresh_token 有 rotation。万一出问题想回滚?或者排查”昨天用的是哪个 token”?

快照方案:

Terminal window
# 每次修改前自动快照
journal_snapshot "~/.codex/auth.json"
# → ~/.codex/journal/auth.20260406T174402Z.json
# 30+ 天自动清理
find ~/.codex/journal -name "*.json" -mtime +30 -delete

这样既有审计日志,又不会无限膨胀磁盘(假设每个快照 ~4KB,每天最多 10 份,30 天 =1.2MB)。

问题排除与边界情况#

并发 refresh#

场景:在 MacBook 上切换了 work 账号,同时 MBP 也在 refresh。

现象:OpenAI API 返回 "Your access token could not be refreshed because your refresh token was already used"

原因:refresh_token 单次使用,A 机器用掉后,B 机器同时也要用,就冲突了。

解决

  1. 脚本本身是单进程的,不会自己并发
  2. 用户在两台机器上手动操作不会同时发生(物理上只有一个人)
  3. 万一真的冲突了,新的 token 会失败,但旧的 journal 快照还在,可以恢复

跨机器时差#

如果本地 refresh 后还没 scp 到 mbp,而用户立即切到 MBP 运行 codex,会用旧 token。

现象:MBP 上的请求被拒(旧 token 过期)。

解决

  1. 脚本会自动提示”同步到 mbp?”,用户一般会按 y
  2. 如果没同步,下一次 codex-refresh 时会获取新 token 并推过去
  3. 用户可以明确跑 codex-refresh --sync 强制同步

login 时凭据丢失#

场景codex-switch login newaccount 途中,codex login 卡住或失败。

流程

Terminal window
mv ~/.codex/auth.json ~/.codex/auth.json.stash.$$ # 暂存
codex login # 失败了
# → 检查 auth.json 是否存在
if [[ ! -f ~/.codex/auth.json ]]; then
# 恢复原凭据
mv ~/.codex/auth.json.stash.$$ ~/.codex/auth.json
fi

原凭据永远不会丢失,最多浪费一个 stash 临时文件。

使用流程示例#

首次设置#

Terminal window
# 当前已经 `codex login` 过一次,有 auth.json(假设是 rent 账号)
# 1. 命名当前账号
codex-switch create rent -f
# 2. 添加另一个账号
codex-switch login work
# → 浏览器弹出登录
# → 登录 work 账号
# → 保存为 auth_work.json,恢复 auth.json(现在还是 rent)
# → "立即切换到 work?" → y
# → 自动 refresh work 的 token
# → "同步到 mbp?" → y
# 3. 查看现状
codex-switch
# → 列出 rent、work 两个账号,work 是当前激活的
# 4. 随时切换
codex-switch rent
# → refresh rent 的 token,自动同步到 mbp

日常使用#

Terminal window
# 需要用 work 账号跑 codex
codex-switch work # 一条命令,搞定
# 需要手动刷新(离线后重连)
codex-refresh --sync
# Token 快过期了(剩 1 天),提前刷新
codex-refresh

相关工具生态#

社区里确实有类似的方案。WebSearch 发现了几个:

现有工具

  • CCS (Claude Code Switch) — 最成熟的方案,v3.0,支持 Claude API、OAuth、其他模型(Gemini、Copilot)
  • cc-switch / claude-swap / claude-code-switch — 各种社区实现
  • 简单方案:用环境变量 + shell alias(CLAUDE_CONFIG_DIR=~/.claude-work 隔离凭据)

现有方案的局限

  • 都是本地多账号管理
  • 凭据存 macOS Keychain(不可 SSH 传输)
  • 没有跨机器同步能力
  • 没有 token 快照审计
  • 没有自动刷新机制(Codex 的特有需求)

所以 codex-switch 的独特之处就在这里:为了跨机器 SSH 同步而重新设计凭据存储和刷新流程

关键洞察#

为什么不用现有工具?#

CCS 和其他工具都很成熟,但它们解决的问题是”本地机器上快速切换”。一旦加上”SSH 同步”这个约束,就完全变了:

  1. Keychain 不可 SSH 传输 — 现有工具都依赖 macOS Keychain,无法跨机器同步
  2. 凭据必须是文本文件 — 才能 scp,所以必须重新设计存储结构
  3. Token 刷新需要自动化 — OpenAI OAuth 的 token 有生命周期,需要定期刷新并自动推到远程

与其改现有工具(破坏它的设计),不如从头写一个为 SSH 同步优化的版本。

为什么 auth.json 和 auth_.json 要双写?#

  • auth.json 是 Codex 实际读的文件
  • auth_<name>.json 是你的”源”,防止失误覆盖
  • 两个同步意味着:无论从哪个文件操作,另一个都是最新的

用 Codex 时读 auth.json,但如果要查历史、对比账号状态,看 auth_* 们。

Token 刷新前为什么要快照?#

因为 refresh_token 是旋转的,旧的作废。如果刷新失败了,旧 token 在快照里,可以手动恢复。Plus 审计日志。

交付物#

三个脚本 + README,已上传 GitHub Gist:

https://gist.github.com/StevenLi-phoenix/015548b4e92630139992136459df5e4d

安装

Terminal window
curl -fsSL https://gist.github.com/StevenLi-phoenix/015548b4e92630139992136459df5e4d/raw/codex-switch -o ~/.local/bin/codex-switch
curl -fsSL https://gist.github.com/StevenLi-phoenix/015548b4e92630139992136459df5e4d/raw/codex-refresh -o ~/.local/bin/codex-refresh
curl -fsSL https://gist.github.com/StevenLi-phoenix/015548b4e92630139992136459df5e4d/raw/codex-journal -o ~/.local/bin/codex-journal
chmod +x ~/.local/bin/{codex-switch,codex-refresh,codex-journal}

修改 SYNC_HOSTS 数组里的 mbp 为你的 SSH 别名即可。

收获#

最大的收获是理解了 OAuth token rotation 的真实成本。单次使用 = 并发不友好。但这种设计确实更安全:一旦 token 泄露,黑客只能用一次,下一次刷新就无法再用了。

另一个是无感跨机器同步有多重要。写这工具之前每次切账号都得 scp 文件。现在一条命令搞定,包括 sync。人机交互就是这样,少一步询问能显著提升体验。

最后是 Bash 脚本依然强力。1000 行代码搞定的事,你用 Python 可能得 2000 行。Shell 就是好。

codex-switch — Codex 多账号管理工具
https://blog.lishuyu.top/posts/codex-switch/
作者
猫猫魔女
发布于
2026-04-06
许可协议
CC BY-NC-SA 4.0