自己一直在用纯血的 Codex CLI,平时基本不折腾花里胡哨的套壳。
这段时间部门里也慢慢开始统一用 Codex,不过接入方式比较杂,有人走官方授权,有人走中转,也有人直接配 API Key。

后来我自己也把使用方式从原来的 OAuth 切到了 API Key,顺手把模型入口收拢了一下,一边接 Codex 后端,一边也把国内几个模型挂进去,图个统一。

结果最先踩到的坑,不在模型能力,也不在额度,而是在历史记录上。

切完之后,历史会话列表几乎看不到了。

刚开始我还以为是显示异常,因为拿旧的 session_id 去执行 codex resume <session_id>,会话照样能拉起来。后来机器重启过一次,再回来看,列表还是不对,这时候基本就能判断出来:会话文件多半还在,只是本地索引没跟上。

我当时的配置大致像这样,在 ~/.codex/config.toml 里把模型提供商切成了自定义 API Key:

model_provider = "custom"
model = "gpt-5.5"
model_reasoning_effort = "high"
disable_response_storage = true

[model_providers.custom]
name = "custom"
wire_api = "responses"
requires_openai_auth = false
supports_websockets = false
personality = "pragmatic"
model_reasoning_effort = "medium"
base_url = "{host_url}"
env_key = "API_KEY"

问题基本就出在这里。
provider 切掉之后,Codex 当前加载的是一套新的使用上下文,但原来那些会话文件并不会自己消失。

历史通常还在本地

如果你也碰到类似情况,先别急着删配置或者重装,先去看本地目录。

我这边实际能看到的几个文件是:

  • ~/.codex/sessions/:原始会话 rollout 文件
  • ~/.codex/session_index.jsonl:会话索引
  • ~/.codex/history.jsonl:历史输入记录
  • ~/.codex/state_5.sqlite:本地线程状态库

真正管用的东西,其实都在 sessions/ 下面。
每次会话都会落成一个 jsonl 文件,路径大概像这样:

~/.codex/sessions/2026/05/17/rollout-2026-05-17T12-28-52-019e3431-b161-7f12-9e91-cd1100b05c9d.jsonl

随便打开一个,前几行通常就能看到类似的元信息:

{
  "type": "session_meta",
  "payload": {
    "id": "019e3431-b161-7f12-9e91-cd1100b05c9d",
    "cwd": "/path/to/project",
    "model_provider": "custom"
  }
}

这里面已经够你恢复会话用了:

  • id:拿去 codex resume 就能直接恢复
  • cwd:当时在哪个工程目录里开的会话
  • model_provider:这次会话归属的 provider

所以只要 sessions/ 还在,严格来说历史并没有真丢。

为什么 resume 能用,列表却没了

我后来翻本地数据的时候,慢慢把这个事看明白了。
Codex 显示历史列表,不是单看 sessions/

按我本机现在的结构,至少能拆成两层来看:

  • sessions/*.jsonl 保存原始会话过程
  • session_index.jsonlstate_5.sqlite 负责展示和检索这一层

所以会出现一个很别扭的状态:

  • codex resume <session_id> 之所以还能工作,是因为原始 rollout 文件还在
  • 历史列表之所以消失,是因为本地索引没有正确同步回来

如果只盯着文件在不在,很容易误判成数据没了。实际上要补的,不是哪一条会话本身,而是 rollout 文件和本地索引之间那层关系。

我的工具实际补了什么

这也是我后来把它单独整理成一个开源项目的原因:

https://github.com/pangkk18/codex-history-sync

https://gitee.com/duke/codex-history-sync

这套东西说穿了也不复杂,就是把 sessions/ 当成事实来源,然后再把上层索引一点点补回去。

先做备份,再碰本地状态。

我本机跑完之后,会先在 ~/.codex/history-sync-backups/ 下生成一个备份包,里面至少会带上这些文件:

  • config.toml
  • state_5.sqlite
  • session_index.jsonl
  • sessions/ 下的 rollout 文件

这一层一定要先做。Codex 本地结构不是铁板一块,尤其是 SQLite 那部分,版本一变就可能不一样,先留回滚点最稳。

备份完之后,再去扫 ~/.codex/sessions/YYYY/MM/DD/*.jsonl,把每个 rollout 里能用的元数据抽出来。

一般会抓这些东西:

  • session_meta.payload.id
  • session_meta.payload.cwd
  • session_meta.payload.model_provider
  • rollout 文件路径
  • 首条用户输入,或者可用的 thread title
  • 最后一次事件时间,作为 updated_at

这一步做完之后,那些原本只能拿来 resume 的会话,才会慢慢变成历史列表能认的记录。

接着就是回写 session_index.jsonl

这一层常见的字段大概有这些:

  • id
  • thread_name
  • updated_at
  • cwd
  • rollout_path

这一层如果缺了,或者和当前状态对不上,列表要么显示不全,要么干脆不显示。

history.jsonl 也顺手补一下。这个文件不一定直接决定最终列表,但会影响本地历史输入体验,所以我会尽量从 rollout 里的首条用户消息或者历史事件里把文本带回来。

还有一层很容易被漏掉,就是 state_5.sqlite 里的线程表。

我本机里 threads 表至少有这些关键字段:

  • id
  • rollout_path
  • created_at
  • updated_at
  • source
  • model_provider
  • cwd
  • title

如果只修 session_index.jsonl,但 SQLite 里的线程状态没补回来,很多时候 TUI 里看到的历史还是残的。

所以这类工具真正干的事情,不是改某一个文件,而是把下面这条链路重新接起来:

sessions/*.jsonl
    -> session_index.jsonl
    -> history.jsonl
    -> state_5.sqlite / threads

为什么我比较相信这种做法

原因很简单,sessions/ 才是最接近原始事实的那层数据。

session_index.jsonl 可以丢,history.jsonl 可以乱,state_5.sqlite 也可能因为版本升级、provider 切换或者异常退出出现不一致,但 rollout 原始文件通常还在。

恢复逻辑只要立在 rollout 上,事情就会稳很多:

  • 幂等,可以重复执行
  • 可验证,恢复前后能直接对比 session 数量
  • 可回滚,因为前面已经先做了备份
  • 不依赖当前 provider 和旧 provider 必须完全一致

说白了,索引这层本来就可能失真,别太相信它。

这类工具适合什么场景

我自己觉得,下面几种情况最常见:

  • 从 OAuth 切到 API Key 或自定义 provider 后,旧历史不显示
  • 迁移机器后把 ~/.codex 拷回来了,但历史列表不完整
  • 本地状态库损坏,resume 还能用,但列表空了
  • 想把已有 rollout 批量同步回当前 Codex 的历史视图

如果你只是临时恢复单个会话,其实一个 codex resume <session_id> 就够了。
但如果你想把整个历史列表重新接回来,还是得老老实实走一遍扫描原始会话、重建索引这条路。

最后

这次踩坑,倒不只是把历史记录救回来了。
更直接的感受是,很多看起来像数据丢失的问题,其实只是展示层或者索引层乱了。

Codex 这类本地优先的工具,其实已经把很多有价值的数据落到了磁盘上。
只要别在慌乱中先把目录删了,通常都还有救。

所以如果你也遇到过类似情况,先别急着重装,也别急着怀疑自己号没了。
先去看看 ~/.codex/sessions/ 还在不在,再决定下一步。

单条恢复,codex resume 就行。
整批恢复,就把索引层一起补上。

我把这套逻辑整理成了开源项目,按需取用就好:
GitHub
GitHub https://github.com/pangkk18/codex-history-sync

码云Gitee
Gitee https://gitee.com/duke/codex-history-sync

后面 Codex 的本地表结构如果再变,比如 state_5.sqlite 后面换成别的版本号,我估计处理思路也不会差太多。
先认准原始会话文件,再去修上层索引,基本都还能往回捞。

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐