清理 Codex 对话残留失效索引:解决侧边栏打不开或反复报错
使用 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 扩大范围。
使用前的准备
- 先完全退出 Codex 桌面应用,包括托盘中的后台进程。
- 准备好 Python 3,并把脚本放到一个自己能确认的目录,例如
D:\tools\codex-cleanup。 - 确认当前用户目录下的
.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 | python .\cleanup_codex_orphan.py --scan --include-archived |
批量写入时,脚本会做两类操作:
- 从全局 JSON 中移除与线程 ID 对应的提示历史、权限心跳、线程描述、客户端绑定和项目关联索引。
- 从 SQLite 中删除线程的动态工具记录、父子线程边记录以及
threads主记录。
删除数据库关联记录后再删除主线程记录,可以避免留下明显的孤立关系。
只处理一条线程
如果已经从错误信息中拿到了线程 ID,可以先预览:
1 | python .\cleanup_codex_orphan.py ` |
确认后追加 --apply:
1 | python .\cleanup_codex_orphan.py ` |
也可以把报错中的 rollout 路径直接传入。路径只要包含标准 UUID,脚本就能提取线程 ID:
1 | python .\cleanup_codex_orphan.py ` |
如果传入的文件实际上仍然存在,脚本会主动退出,不执行清理,避免误删有效线程。
日志扫描模式
某些情况下,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 | python .\cleanup_codex_orphan.py ` |
这些参数只覆盖本次执行,不会修改 Codex 的配置。建议先用 --help 查看当前脚本版本支持的完整参数:
1 | python .\cleanup_codex_orphan.py --help |
备份与恢复
使用 --apply 时,脚本会在原文件旁生成带时间戳的备份,例如:
1 | state_5.sqlite.before-cleanup-batch-20260821-085643.bak |
如果清理后发现结果不符合预期,先退出 Codex,再用明确的备份文件覆盖当前文件:
1 | Copy-Item ` |
恢复后重新启动 Codex,并观察侧边栏和日志是否恢复正常。备份文件确认无用后,再手动删除,避免误删后失去回滚依据。
常见问题
database is locked
通常说明 Codex 或其他进程仍在使用 SQLite。确认 Codex 已完全退出后再重试;必要时在任务管理器中检查残留进程。
扫描结果为 0
可能是当前没有指向缺失 rollout 的未归档线程、记录已经被清理,或实际使用的是另一个状态库。可以尝试 --include-archived,或者使用 --database 指定正确的 SQLite 文件。
清理后侧边栏仍有旧记录
先完全退出并重新启动 Codex,再确认 dry-run 输出。如果记录仍存在,检查是否有多个用户目录、多个状态库,或该条记录来自另一台设备的同步状态。
边界与风险
这个脚本清理的是本机 Codex 索引,不等同于账户层面的聊天删除。它也不会恢复已经丢失的 rollout 内容。执行前请保留备份,并只对确认无效的线程使用 --apply。
如果只是想隐藏一条仍然有效的历史对话,应使用 Codex 自带的归档功能,而不是把它当作“缺失文件”强行清理。归档、删除和本地索引修复是三个不同层次的操作,分清目标后再选择命令,风险最低。
