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

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 没有正确把它们挂回当前项目。

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 的核心流程是:

  1. 解析 CODEX_HOME
  2. 解析目标项目 root
  3. 先 dry-run 审计,不直接写入
  4. 检查当前项目历史 Session 数量
  5. 检查 session_index.jsonl 是否缺记录
  6. 检查 .codex-global-state.json 是否缺 project/sidebar 映射
  7. 检查 SQLite 中的 threads.cwd
  8. 判断是否存在 Windows \\?\ 路径精确匹配风险
  9. 写入前自动创建时间戳备份
  10. 必要时规范化 cwd
  11. 恢复后再次 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: 0
  • missing_global_hints: 0
  • missing_project_assignments: 0
  • missing_sidebar_order: 0
  • cwd_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 能识别的项目状态里。

相关推荐
IT飞牛2 小时前
【Codex实战】创建永久工作树、派生到本地/新工作树、分叉的区别
ai·codex
এ慕ོ冬℘゜3 小时前
手写一款高兼容、零BUG图片预览组件|前端
前端·bug
Soari6 小时前
Codex CLI 安装 ERR解决
codex
csdnor_0119 小时前
Codex CLI 使用 Ollama 本地模型
解决方案·codex·ollama·gpt-oss·codex-cli
xzzd_jokelin20 小时前
公司AI开发痛点解析:多人+AI辅助 协同开发?
人工智能·机器学习·ai·ai编程·cloud·codex
俯首甘为孺子刘x1 天前
AI时代的焦虑与思考
人工智能·ai编程·codex·ai-agent
大强同学1 天前
我用 Claude Code,把 NotebookLM 变成了 Obsidian 插件
人工智能·agent·claude·skill·notebooklm
hui函数1 天前
Python系列Bug修复|如何解决 pip install 报错 ModuleNotFoundError: No module named ‘pygame’ 问题
python·bug·pip