背景
我的 Codex CLI 一直通过一个 CRS 中转 API 使用,所有历史会话、项目配置、MCP 都在默认的 ~/.codex/ 里。后来开了 ChatGPT 订阅,想把默认命令改成订阅 OAuth 登录。
最省事的做法是新建一个空的 CODEX_HOME,重新登录。但这样 cdx resume 就看不到原来的会话了。我当时本地有 5,000 多条会话,分布在 200 多个项目目录里,不想丢。
最后的结果:原目录原地切换,迁移前后会话记录逐条比对,ID、项目目录、rollout 路径、provider 字段的缺失和变化都是 0。
环境:macOS,Codex CLI 0.162.x。
最终的命令布局
我把三种用法拆成三个独立实例,互不污染:
| 命令 | 数据目录 | 用途 |
|---|---|---|
cdx | ~/.codex/ | 默认入口,ChatGPT 订阅 OAuth,保留全部历史 |
cdxs | ~/.codex-sub/ | 另一套保留的实例 |
cx | ~/.codex-crs/ | 偶尔还要用中转 API 时用,独立 API Key |
别名写在 zsh 配置里:
CODEX_NODE=22.23.1
alias cdx="mise exec node@${CODEX_NODE} -- codex --yolo"
alias cdxs="CODEX_HOME=$HOME/.codex-sub mise exec node@${CODEX_NODE} -- codex --yolo"
alias cx="CODEX_HOME=$HOME/.codex-crs mise exec node@${CODEX_NODE} -- codex --yolo"cdx 没有显式设 CODEX_HOME,用的是默认目录。要注意:如果当前 shell 已经 export 过别的 CODEX_HOME,cdx 会进错实例。不确定的时候写成 CODEX_HOME="$HOME/.codex" cdx。
cx 是单独配的,没复制 cdx 的历史和 MCP。中转模式要回来,用它就行,不用动主实例。
保留历史的关键:provider 名字不动,只改地址
翻了一下历史会话,每条记录的 model_provider 都是 crs。如果把 provider 换成新名字,旧会话和新配置就对不上了。
所以我保留 crs 这个标识,只把它指向的地址改成 ChatGPT 官方端点:
model_provider = "crs"
model = "gpt-6.1-sol"
[model_providers.crs]
name = "crs"
base_url = "https://chatgpt.com/backend-api/codex"
wire_api = "responses"
requires_openai_auth = true这只是相关字段,不要拿去覆盖整份 config.toml,其他设置保持原样。另外,顶层字段(model_provider、model)必须放在第一个 [表] 之前,否则 TOML 会把它们归到上一个表里。
改完之后,crs 只是个历史兼容的名字,实际请求已经直接发到官方,不再经过中转。
整个过程没改历史数据库,也没重写任何会话文件。
操作步骤
1. 先备份
完整备份数据目录,再单独存一份 SQLite 一致性快照。活跃的数据库不能只复制主文件,WAL 里可能还有没合并的数据。运行中的 socket 是进程通信端点,不用备份。
sqlite3 ~/.codex/state_5.sqlite ".backup '$HOME/codex-backups/state_5.consistent.sqlite'"(数据库文件名以你本机实际为准。)
2. 改 provider 地址
按上一节改 config.toml。顺序不能反:必须先把地址改成官方端点,再做 OAuth 登录。否则 ChatGPT 的 OAuth token 会被发到原来的中转地址。
3. 浏览器授权
CODEX_HOME="$HOME/.codex" cdx login
CODEX_HOME="$HOME/.codex" cdx login status看到 Logged in using ChatGPT 就算成功。auth.json 里的鉴权类型也会从 API Key 变成 ChatGPT OAuth tokens。检查这个文件时只看类型和字段是否存在,不要把内容打印出来。
坑:登录成功了,新会话还显示 API key
登录成功后,我打开一个新的 cdx 会话,/status 里依然显示 API key configured。
原因是 Codex 有一个本地后台 app-server,新开终端不一定会启动新的 server,它手里还拿着旧的鉴权状态。
先用不走后台 server 的独立进程确认配置本身没问题:
CODEX_HOME="$HOME/.codex" cdx --no-daemon独立进程正常,就说明是后台 server 缓存了旧状态。重启它:
CODEX_HOME="$HOME/.codex" mise exec node@${CODEX_NODE} -- codex app-server daemon restart重启会中断所有连在这个 server 上的会话,要在没有重要任务跑的时候做。我这次等了大约一分钟,旧进程退出,新 server 起来了。
再开 cdx,/status 里应该看到:
- provider 仍然是
crs - Account 显示订阅账号和套餐
- 能读到 Weekly limit
最后发一个不调工具的简单请求,返回 OK,说明官方端点实际可用。跨项目看历史:
cdx resume --all注意:能看到历史列表,不代表每个旧会话都能恢复运行。我只验证了数据库记录完整,没有逐个恢复全部会话。
踩过和差点踩的坑
- 不要给 OAuth 实例随手加
forced_login_method = "api"。 我后来跑一个 DeepSeek 接入脚本时它加了这个字段,结果鉴权冲突,直接被登出。 -
login status只代表独立 CLI 进程的状态,不代表后台 server。 要结合新会话里的/status、数据目录和后台进程一起判断。 - provider 名字说明不了请求发到哪、怎么计费。 我这里叫
crs,实际地址是官方端点,用的是订阅。 - 排查时不要打印完整配置、API Key 或 OAuth token。 备份目录也要收紧权限,里面有登录凭据和私人会话内容。
- 当前版本会把
preferred_auth_method和disable_response_storage当成忽略项。 迁移能成功,靠的是 OAuth 凭据、provider 地址和requires_openai_auth这三样,不要以为写了那两个字段就能保证鉴权。
回滚原则
- 要用中转,优先用已经隔离好的
cx,不要改回主实例。 - 确实要回滚
cdx:先停掉相关工作,备份迁移后新增的数据,再只恢复必要的配置和鉴权文件。不要整个目录覆盖回去,那会把备份之后新产生的会话全部丢掉。
参考
「保留 crs 标识、替换官方地址」是我在当前版本上实际验证过的方案。Codex 升级以后,要重新检查一遍鉴权、请求地址和历史是否可见。