Codex 更新后历史 Session 消失?我写了一个修复官方 Bug 的 Recovery Skill

数据其实还在,只是新版 Codex 暂时看不见了。
最近 Codex Desktop 更新之后,我遇到了一个很吓人的问题:当前项目的历史 Session 不见了。
侧边栏里只剩下当前会话,过去几天、甚至更早的项目上下文像是被清空了一样。
第一反应当然是:是不是数据丢了?
但我没有马上重装,也没有急着清缓存,而是先去看本地 .codex 目录。结果发现,很多历史数据其实还在:
sessions/**/*.jsonl还在state_5.sqlite里还能查到 thread- 部分历史 rollout 文件也还在

这张图能说明一个关键点:历史 Session 对应的本地文件并没有消失。
也就是说,这不是典型意义上的"数据丢失"。
更准确地说,这是一次 Codex Desktop 更新之后,旧版本 Session 元数据和新版 UI / 索引 / 项目映射规则之间的不兼容。
于是我把这次恢复过程沉淀成了一个 Codex Skill:codex-session-recovery。
它的目标很简单:在官方彻底修复之前,尽可能安全地把本地历史 Session 找回来。
Skill 已经发布到 GitHub:
这不是个例
GitHub 上已经有不少用户反馈过类似问题。典型现象包括:
- Codex Desktop 更新后,project chat history 消失
- 项目侧边栏显示
No chats - 本地
state_5.sqlite里还有 threads,但 UI 不显示 - Windows / WSL / workspace path 迁移后,旧 Session 无法被新版本匹配
可以参考这些相关 issue:
- Codex Desktop project chat histories disappeared after recent update
- Codex Desktop project sidebar shows No chats while local sessions still exist
- Codex Desktop on Windows no longer shows older chats after update
- Codex Desktop local project conversation history missing after update, threads still exist in state_5.sqlite
- Codex Desktop project chat history disappears after update due to WSL cwd/path migration mismatch
这类问题最容易让人误判成"历史记录被删了"。
但从本地文件看,很多时候真实情况是:
旧数据还在,新版 Codex 没有正确把它们挂回当前项目。
Codex 的历史 Session 不只存在一个文件里
恢复这个问题之前,需要先理解 Codex Desktop 的本地状态大概分散在哪些地方。
至少有几类文件会参与历史 Session 的显示:
| 文件 / 数据源 | 作用 |
|---|---|
sessions/**/*.jsonl |
保存真实会话 rollout 数据 |
session_index.jsonl |
轻量 Session 索引 |
state_5.sqlite |
保存 thread 状态、cwd、标题等信息 |
.codex-global-state.json |
保存项目、侧边栏、workspace 关联状态 |
这也是为什么"只改一个文件"往往不够。
如果 sessions 还在,但 session_index 缺记录,历史会话可能无法被索引到。
如果 SQLite 里 thread 还在,但 .codex-global-state.json 缺少 project assignment 或 sidebar order,UI 也可能不显示。
如果路径不完全一致,新版 UI 查询时也可能匹配不到。
真正难处理的是 cwd 精确匹配
我一开始以为,把 session_index.jsonl 补回来就差不多了。
后面发现真正坑人的地方是 cwd。
新版 Codex 的 thread list 查询更像是按照 cwd 做精确匹配。也就是说:
D:\workspace\mygithub\streamlinker\\?\D:\workspace\mygithub\streamlinker
这两个路径对 Windows 文件系统来说可能指向同一个目录。
但对 UI 查询来说,它们可能就是两个不同字符串。
结果就是:
SQLite 里明明有 thread,新版 UI 却查不到。
这个问题在 Windows 上尤其容易出现,因为旧版本或某些运行路径可能写入 \\?\ 扩展路径,而新版 UI 当前项目 root 使用普通路径。
不能直接手改,还有一个原因
这类修复不能随便打开文件手工改。
原因是 Codex Desktop 本身正在运行时,Electron 进程可能会把内存里的旧状态重新写回磁盘。
你以为自己修好了 .codex-global-state.json,结果应用一退出,又被覆盖回去了。
所以安全恢复流程必须先判断:
- Codex 是否正在运行
- 当前是否可以安全写 global state
- SQLite 是否存在 cwd 精确匹配风险
- 写入前是否已经备份
- 写入后是否重新 dry-run 验证
这也是我做 codex-session-recovery 的原因。
它不是一个"搜索替换脚本",而是一套带判断、备份、恢复和校验的操作流程。
codex-session-recovery 做了什么
项目地址:
这个 Skill 的核心流程是:
- 解析
CODEX_HOME - 解析目标项目 root
- 先 dry-run 审计,不直接写入
- 检查当前项目历史 Session 数量
- 检查
session_index.jsonl是否缺记录 - 检查
.codex-global-state.json是否缺 project/sidebar 映射 - 检查 SQLite 中的
threads.cwd - 判断是否存在 Windows
\\?\路径精确匹配风险 - 写入前自动创建时间戳备份
- 必要时规范化 cwd
- 恢复后再次 dry-run 校验
一个典型 dry-run 会关注这些字段:
json
{
"exact_project_sessions": 3,
"missing_from_session_index": 0,
"missing_global_hints": 0,
"missing_project_assignments": 0,
"missing_sidebar_order": 0,
"sqlite_exact_ui_cwd_threads": 4,
"sqlite_non_exact_ui_cwd_threads": 0,
"sqlite_extended_path_threads": 0,
"cwd_exact_match_risk": false
}
下面是使用 codex-session-recovery 做 dry-run 审计时的截图。
它的重点不是马上写入修复,而是先把当前项目的本地状态看清楚:历史 Session 数量、global state 映射缺失情况、SQLite 里的 cwd 是否存在精确匹配风险。

我不会把"脚本执行完"当成恢复成功。
真正的成功标准是:
missing_from_session_index: 0missing_global_hints: 0missing_project_assignments: 0missing_sidebar_order: 0cwd_exact_match_risk: false- SQLite 里的 project threads 能被新版 UI 精确匹配
恢复流程图
#mermaid-svg-5XTVemCNyxC8WI6C{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-5XTVemCNyxC8WI6C .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-5XTVemCNyxC8WI6C .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-5XTVemCNyxC8WI6C .error-icon{fill:#552222;}#mermaid-svg-5XTVemCNyxC8WI6C .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-5XTVemCNyxC8WI6C .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-5XTVemCNyxC8WI6C .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-5XTVemCNyxC8WI6C .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-5XTVemCNyxC8WI6C .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-5XTVemCNyxC8WI6C .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-5XTVemCNyxC8WI6C .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-5XTVemCNyxC8WI6C .marker{fill:#333333;stroke:#333333;}#mermaid-svg-5XTVemCNyxC8WI6C .marker.cross{stroke:#333333;}#mermaid-svg-5XTVemCNyxC8WI6C svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-5XTVemCNyxC8WI6C p{margin:0;}#mermaid-svg-5XTVemCNyxC8WI6C .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-5XTVemCNyxC8WI6C .cluster-label text{fill:#333;}#mermaid-svg-5XTVemCNyxC8WI6C .cluster-label span{color:#333;}#mermaid-svg-5XTVemCNyxC8WI6C .cluster-label span p{background-color:transparent;}#mermaid-svg-5XTVemCNyxC8WI6C .label text,#mermaid-svg-5XTVemCNyxC8WI6C span{fill:#333;color:#333;}#mermaid-svg-5XTVemCNyxC8WI6C .node rect,#mermaid-svg-5XTVemCNyxC8WI6C .node circle,#mermaid-svg-5XTVemCNyxC8WI6C .node ellipse,#mermaid-svg-5XTVemCNyxC8WI6C .node polygon,#mermaid-svg-5XTVemCNyxC8WI6C .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-5XTVemCNyxC8WI6C .rough-node .label text,#mermaid-svg-5XTVemCNyxC8WI6C .node .label text,#mermaid-svg-5XTVemCNyxC8WI6C .image-shape .label,#mermaid-svg-5XTVemCNyxC8WI6C .icon-shape .label{text-anchor:middle;}#mermaid-svg-5XTVemCNyxC8WI6C .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-5XTVemCNyxC8WI6C .rough-node .label,#mermaid-svg-5XTVemCNyxC8WI6C .node .label,#mermaid-svg-5XTVemCNyxC8WI6C .image-shape .label,#mermaid-svg-5XTVemCNyxC8WI6C .icon-shape .label{text-align:center;}#mermaid-svg-5XTVemCNyxC8WI6C .node.clickable{cursor:pointer;}#mermaid-svg-5XTVemCNyxC8WI6C .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-5XTVemCNyxC8WI6C .arrowheadPath{fill:#333333;}#mermaid-svg-5XTVemCNyxC8WI6C .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-5XTVemCNyxC8WI6C .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-5XTVemCNyxC8WI6C .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-5XTVemCNyxC8WI6C .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-5XTVemCNyxC8WI6C .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-5XTVemCNyxC8WI6C .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-5XTVemCNyxC8WI6C .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-5XTVemCNyxC8WI6C .cluster text{fill:#333;}#mermaid-svg-5XTVemCNyxC8WI6C .cluster span{color:#333;}#mermaid-svg-5XTVemCNyxC8WI6C div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-5XTVemCNyxC8WI6C .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-5XTVemCNyxC8WI6C rect.text{fill:none;stroke-width:0;}#mermaid-svg-5XTVemCNyxC8WI6C .icon-shape,#mermaid-svg-5XTVemCNyxC8WI6C .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-5XTVemCNyxC8WI6C .icon-shape p,#mermaid-svg-5XTVemCNyxC8WI6C .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-5XTVemCNyxC8WI6C .icon-shape .label rect,#mermaid-svg-5XTVemCNyxC8WI6C .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-5XTVemCNyxC8WI6C .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-5XTVemCNyxC8WI6C .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-5XTVemCNyxC8WI6C :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 否
是
是
否
Codex 更新后历史 Session 消失
检查本地 sessions / SQLite 是否还在
数据还在?
不是本 Skill 处理范围
Dry-run 审计 session_index / global state / cwd
Codex 正在运行?
外部 helper 等待退出或 UAC 关闭
写入修复并自动备份
规范化 Windows cwd / 修复 sidebar 映射
再次 dry-run 校验 missing_* = 0
历史 Session 恢复显示
最近又补了一个隐藏失败路径
这次恢复过程中,我又发现一个容易误判的地方。
当 Codex 正在运行时,Skill 会启动一个外部 PowerShell helper,让它在 Codex 退出后继续恢复。
这里是恢复过程中提示需要关闭 Codex / 交给外部 helper 处理的截图。
这个步骤很重要,因为 Codex Desktop 还在运行时,直接写 .codex-global-state.json 可能会被 Electron 进程的内存状态覆盖。

但在 Windows 上,普通隐藏 PowerShell helper 有时会出现这种情况:
Start-Process返回成功- 但 helper 没有真正接管
- 没有生成日志
- Codex 进程一个都没少
以前这种情况很容易被误判成"恢复正在后台跑"。
后来我把这个失败路径也写进 Skill:
启动 helper 后必须确认日志出现。
如果显式指定 C:\tmp 日志路径后,2-3 秒仍然没有日志,并且 Codex / codex 进程还在,就直接判定:
helper 没有接管,不要重复同一个隐藏命令,改走 UAC 管理员 helper。
这就是 Skill 和普通脚本的区别。
普通脚本通常只写 happy path。
Skill 应该记录真实事故里的判断条件、失败信号和下一步动作。
为什么要做成 Skill,而不是一条命令
因为这个问题不是"一条命令"能稳定解决的。
它更像一个本地数据迁移修复流程:
- 先判断数据是否还在
- 再判断新版 UI 为什么看不见
- 再决定能不能写
- 写之前备份
- 写之后验证
- helper 失败时要能切换路径
这些判断如果每次都靠人脑记,很容易漏。
做成 Skill 之后,下一次遇到类似问题,Codex 可以按固定流程执行:
- 不会一上来就写文件
- 不会把全文搜索误判为项目归属
- 不会忽略
\\?\cwd 风险 - 不会在 Codex 正在运行时贸然写 global state
- 不会把"隐藏 helper 启动成功"误判成"恢复已接管"
结语
这次问题给我的感觉是:很多 AI 工具的本地状态问题,最后并不是靠一个神奇命令解决的。
真正重要的是一套可靠流程:
- 什么现象代表数据还在
- 哪些字段说明索引断了
- 什么路径会导致新版 UI 查不到
- 什么时候不能写
- 什么日志说明 helper 没接管
- 哪些字段归零才算恢复完成
codex-session-recovery 就是这样一个 Skill。
它不是替代官方修复,也不是保证能救回所有情况。
但在官方彻底修复这个升级兼容性问题之前,它至少可以帮我把仍然留在本地磁盘上的历史 Session,安全地重新挂回新版 Codex。
如果你也遇到 Codex 更新后历史 Session 消失,先别急着重装。
先检查一下本地 .codex 目录。
很多时候,历史不是没了,只是新版 Codex 暂时看不见它。
Skill 地址:
下面是已经恢复的项目 session 截图:

这张图和开头形成了前后对比:历史 Session 不是重新生成出来的,而是把仍然存在于本地的数据重新挂回了新版 Codex 能识别的项目状态里。