Codex 历史记录恢复与同步:会话没丢,只是列表不见了
自己一直在用纯血的 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.jsonl和state_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.tomlstate_5.sqlitesession_index.jsonlsessions/下的 rollout 文件
这一层一定要先做。Codex 本地结构不是铁板一块,尤其是 SQLite 那部分,版本一变就可能不一样,先留回滚点最稳。
备份完之后,再去扫 ~/.codex/sessions/YYYY/MM/DD/*.jsonl,把每个 rollout 里能用的元数据抽出来。
一般会抓这些东西:
session_meta.payload.idsession_meta.payload.cwdsession_meta.payload.model_provider- rollout 文件路径
- 首条用户输入,或者可用的 thread title
- 最后一次事件时间,作为
updated_at
这一步做完之后,那些原本只能拿来 resume 的会话,才会慢慢变成历史列表能认的记录。
接着就是回写 session_index.jsonl。
这一层常见的字段大概有这些:
idthread_nameupdated_atcwdrollout_path
这一层如果缺了,或者和当前状态对不上,列表要么显示不全,要么干脆不显示。
history.jsonl 也顺手补一下。这个文件不一定直接决定最终列表,但会影响本地历史输入体验,所以我会尽量从 rollout 里的首条用户消息或者历史事件里把文本带回来。
还有一层很容易被漏掉,就是 state_5.sqlite 里的线程表。
我本机里 threads 表至少有这些关键字段:
idrollout_pathcreated_atupdated_atsourcemodel_providercwdtitle
如果只修 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 后面换成别的版本号,我估计处理思路也不会差太多。
先认准原始会话文件,再去修上层索引,基本都还能往回捞。
更多推荐



所有评论(0)