使用 Codex 一段时间后,偶尔会遇到这样的情况:侧边栏还保留着一条对话,但点击后打不开;日志反复提示某个 rollout 文件不存在;重启 Codex 后,失效记录又重新出现。

这类问题通常不是项目代码损坏,而是本地索引还指向已经被移动或删除的对话记录文件。本文介绍一个 Python 清理脚本 cleanup_codex_orphan.py 的工作原理和使用方法。文中的盘符、目录名和线程 ID 均为脱敏示例,请替换成自己的实际值。

问题是怎么产生的

Codex 本地状态大致分成两部分:

  • SQLite 线程索引:默认位于 %USERPROFILE%\.codex\state_5.sqlite,保存线程 ID、标题和 rollout_path 等信息。
  • 全局 JSON 索引:默认位于 %USERPROFILE%\.codex\.codex-global-state.json,保存侧边栏和客户端关联等状态。

当 SQLite 仍然保存着一条线程记录,但 rollout_path 指向的 JSONL 文件已经不存在时,就形成了“孤儿索引”。只删除 JSONL 文件本身并不能清理这些索引,反而可能让 Codex 每次启动时继续尝试解析失效路径。

脚本只清理索引,不会删除项目代码,也不会删除仍然存在的 rollout 文件。默认扫描未归档线程,因为这类记录更可能直接影响侧边栏;需要时可以通过 --include-archived 扩大范围。

使用前的准备

  1. 先完全退出 Codex 桌面应用,包括托盘中的后台进程。
  2. 准备好 Python 3,并把脚本放到一个自己能确认的目录,例如 D:\tools\codex-cleanup。
  3. 确认当前用户目录下的 .codex 状态文件确实是要处理的那一份。若机器上有多个用户或自定义数据目录,不要直接套用默认路径。

--apply 会修改本地状态文件。第一次执行时建议只做 dry-run,确认输出中的线程和路径都可以清理后再写入。

推荐流程:先扫描,再批量清理

在脚本所在目录执行未归档线程扫描:

1
python .\cleanup_codex_orphan.py --scan

脚本会以只读方式打开 SQLite,检查 threads.rollout_path 指向的文件是否存在,并列出发现的线程 ID、缺失路径以及将要移除的 JSON 索引字段。这个命令不会修改任何文件。

确认结果无误后,执行实际清理:

1
python .\cleanup_codex_orphan.py --scan --apply

如果要把已归档线程也纳入扫描:

1
2
python .\cleanup_codex_orphan.py --scan --include-archived
python .\cleanup_codex_orphan.py --scan --include-archived --apply

批量写入时,脚本会做两类操作:

  • 从全局 JSON 中移除与线程 ID 对应的提示历史、权限心跳、线程描述、客户端绑定和项目关联索引。
  • 从 SQLite 中删除线程的动态工具记录、父子线程边记录以及 threads 主记录。

删除数据库关联记录后再删除主线程记录,可以避免留下明显的孤立关系。

只处理一条线程

如果已经从错误信息中拿到了线程 ID,可以先预览:

1
2
python .\cleanup_codex_orphan.py `
--thread-id "aaaaaaaa-bbbb-4ccc-8ddd-eeeeeeeeeeee"

确认后追加 --apply:

1
2
3
python .\cleanup_codex_orphan.py `
--thread-id "aaaaaaaa-bbbb-4ccc-8ddd-eeeeeeeeeeee" `
--apply

也可以把报错中的 rollout 路径直接传入。路径只要包含标准 UUID,脚本就能提取线程 ID:

1
2
python .\cleanup_codex_orphan.py `
--rollout-path "C:\Users\example\.codex\sessions\2026\08\20\rollout-2026-08-20T11-16-11-aaaaaaaa-bbbb-4ccc-8ddd-eeeeeeeeeeee.jsonl"

如果传入的文件实际上仍然存在,脚本会主动退出,不执行清理,避免误删有效线程。

日志扫描模式

某些情况下,SQLite 索引不完整,但 rollout 日志里明确记录了类似下面的错误:

1
failed to resolve rollout path `...jsonl`: file does not exist

此时可以扫描默认 sessions 目录:

1
python .\cleanup_codex_orphan.py --scan-logs

日志扫描会识别错误中的缺失路径,提取线程 ID,再预览全局 JSON 中可移除的索引。它是补充手段,完整性通常不如 SQLite 扫描;如果还需要同步删除 SQLite 线程行,优先使用 --scan 或 --thread-id 再复核一次。

自定义状态文件位置

如果 Codex 使用了非默认目录,可以显式传入状态库、JSON 状态和 sessions 目录:

1
2
3
4
5
python .\cleanup_codex_orphan.py `
--scan `
--database "D:\codex-data\state_5.sqlite" `
--state-file "D:\codex-data\.codex-global-state.json" `
--sessions-dir "D:\codex-data\sessions"

这些参数只覆盖本次执行,不会修改 Codex 的配置。建议先用 --help 查看当前脚本版本支持的完整参数:

1
python .\cleanup_codex_orphan.py --help

备份与恢复

使用 --apply 时,脚本会在原文件旁生成带时间戳的备份,例如:

1
2
state_5.sqlite.before-cleanup-batch-20260821-085643.bak
.codex-global-state.json.before-cleanup-batch-20260821-085643.bak

如果清理后发现结果不符合预期,先退出 Codex,再用明确的备份文件覆盖当前文件:

1
2
3
4
5
6
7
8
9
Copy-Item `
"D:\codex-data\state_5.sqlite.before-cleanup-batch-20260821-085643.bak" `
"D:\codex-data\state_5.sqlite" `
-Force

Copy-Item `
"D:\codex-data\.codex-global-state.json.before-cleanup-batch-20260821-085643.bak" `
"D:\codex-data\.codex-global-state.json" `
-Force

恢复后重新启动 Codex,并观察侧边栏和日志是否恢复正常。备份文件确认无用后,再手动删除,避免误删后失去回滚依据。

常见问题

database is locked

通常说明 Codex 或其他进程仍在使用 SQLite。确认 Codex 已完全退出后再重试;必要时在任务管理器中检查残留进程。

扫描结果为 0

可能是当前没有指向缺失 rollout 的未归档线程、记录已经被清理,或实际使用的是另一个状态库。可以尝试 --include-archived,或者使用 --database 指定正确的 SQLite 文件。

清理后侧边栏仍有旧记录

先完全退出并重新启动 Codex,再确认 dry-run 输出。如果记录仍存在,检查是否有多个用户目录、多个状态库,或该条记录来自另一台设备的同步状态。

边界与风险

这个脚本清理的是本机 Codex 索引,不等同于账户层面的聊天删除。它也不会恢复已经丢失的 rollout 内容。执行前请保留备份,并只对确认无效的线程使用 --apply。

如果只是想隐藏一条仍然有效的历史对话,应使用 Codex 自带的归档功能,而不是把它当作“缺失文件”强行清理。归档、删除和本地索引修复是三个不同层次的操作,分清目标后再选择命令,风险最低。

脚本下载

脚本文件:https://imgs.wybyte.top/file/cleanup_codex_orphan.py