历史任务从侧边栏消失时,人很容易立刻进入“恢复数据”的思路:换一份数据库、重建索引,或者把旧目录覆盖回来。问题在于,侧边栏空白只是一种界面现象。正文文件、任务元数据、列表查询和历史分页分别位于不同层级,任何一层出现偏差,都可能制造相似的表象。
可靠的排障顺序,是先证明数据处于什么状态,再决定是否需要修复;每一次修改都应晚于相应证据。
这套方法来自一次历史任务不可见故障的完整复盘。如果想先理解 rollout、SQLite、Provider 与列表之间的关系,可以阅读任务存储与生命周期模型。本文只关注遇到故障后怎样缩小范围,以及怎样避免把仍然完整的数据越修越乱。
文中的 state_5.sqlite 字段、App Server RPC 和 Provider 实验对应 0.145.0-alpha.18 的本机观察。公开环境变量以当前官方文档为准;内部表结构和客户端调用方式可能随版本变化。
先判断故障落在哪一层
“历史任务消失”可以拆成几类彼此不同的问题:
| 表象 | 优先检查的层级 | 首要证据 |
|---|---|---|
| 侧边栏没有旧任务 | 发现层 | thread/list 条件、数据库任务行 |
| 归档页为空 | 发现层 | archived 与 Provider 条件 |
| 项目下任务不完整 | 项目映射或发现层 | 项目映射、无项目集合、列表结果 |
| 已知 ID 能返回元数据 | 定位层正常 | thread/read 响应、数据库行 |
| 界面或响应包含历史 | 读取层正常 | includeTurns、rollout 解析 |
| 打开后只有最近几轮 | 历史分页层 | turns cursor、持续向前加载 |
| 可以阅读但不能继续发送 | 恢复与运行层 | Provider、认证、模型与传输 |
| rollout 文件缺失 | 物理数据层 | 数据库路径与文件存在性 |
这张表的价值在于,它把一个模糊的“历史没了”改写成几个可以分别验证的问题。完整报错、任务 ID、文件状态和查询结果,比侧边栏截图更接近根因。
第一步永远是保护现场
在根因未知时,先停止这些操作:
- 用旧快照覆盖当前状态目录;
- 批量修改 SQLite;
- 拼接或重写 rollout;
- 同时调整 Provider、项目映射和索引;
- 删除旧目录或迁移半成品;
- 在 Codex 运行时替换活动数据库。
活动任务的 JSONL 可能仍在追加。一个看似“更正常”的旧快照,可能恰好缺少最近几轮对话、工具调用和最有价值的调查证据。
时间点快照至少应保存:
config.toml
state_5.sqlite 及相关 WAL 文件
sessions/
archived_sessions/
session_index.jsonl
全局状态文件
当前 Codex 版本
CODEX_HOME 与 CODEX_SQLITE_HOME
快照时间
正式快照、目录替换和激活操作应在 Codex 窗口、系统托盘和相关后台进程完全退出后进行。运行中可以做只读盘点,但它只能描述当前时刻,无法替代一致性快照。
用只读证据回答三个问题
第一轮调查只需要回答:正文有没有、元数据有没有、路径是否能把两者连起来。
rollout 是否仍在
先检查 sessions/ 与 archived_sessions/,再用任务 ID 定位文件。标题、项目名和侧边栏位置都可能变化,任务 ID 更适合作为恢复锚点。
找到候选 rollout 后,确认:
- 文件能够逐行解析;
- 首部
session_meta.payload.id与目标任务一致; - 尾部仍有预期的近期消息;
- 文件长度与修改时间符合任务活动情况;
- 多份副本之间是否存在严格前缀关系。
如果标题搜索失败,但对应 rollout 仍然存在,“物理删除”就不应继续作为首要假设。
SQLite 是否完整
对数据库副本执行只读检查:
PRAGMA integrity_check;
预期结果为:
ok
随后检查任务 ID、rollout_path、归档状态和 Provider。完整性通过只能排除部分数据库损坏,无法证明列表条件正确,也无法证明路径指向的正文仍然存在。
路径是否自洽
本机版本的 threads.rollout_path 使用绝对路径。需要逐项确认:
任务 ID 存在于 SQLite
↓
rollout_path 指向实际文件
↓
文件中的 session_meta.payload.id 与任务 ID 相同
数据库位于新目录、rollout 位于新目录、数据库路径指向这份 rollout,是三个需要分别成立的条件。
建立假设矩阵
只读取证完成后,不要急着选择最顺眼的解释。把假设、证据和修改风险放在同一张表里:
| 假设 | 支持证据 | 排除方式 | 风险 |
|---|---|---|---|
| rollout 被删除 | 路径缺失 | 搜索旧根目录与快照 | 低,只读 |
| SQLite 损坏 | 完整性失败 | PRAGMA integrity_check | 低,只读 |
| 项目映射损坏 | 只在特定项目缺失 | 无项目查询、按 ID 读取 | 中 |
| Provider 筛选 | 配置切换后列表变化 | 同库、不同配置 A/B | 低,隔离副本 |
| 长任务被截断 | 只显示最近几轮 | 分页与冷启动对照 | 低 |
| 旧格式不兼容 | 干净配置也无法读取 | 代表性样本迁入 | 中 |
| 跨盘迁移不完整 | 旧目录继续增长 | 检查路径与新消息位置 | 低,只读 |
每次实验都记录“支持或排除了什么”。单纯写下“失败”会丢掉最重要的信息:调查范围究竟缩小了多少。
用单变量 A/B 停止猜测
当几种假设都说得通时,最有效的实验通常很朴素:准备两个隔离环境,让它们只差一个变量。
常见组合包括:
同一数据库 + 不同 config.toml
同一 config.toml + 不同 thread/list 参数
同一任务 + 多次冷启动
同一 rollout + 有无额外历史锚点
同一迁移 stage + 不同默认 Provider
如果一次同时改动数据库、项目映射、索引、Provider 和正文,即使界面恢复,也很难知道究竟是哪一步起了作用。
Provider A/B 的实际结果
在那次故障中,三个隔离 CODEX_HOME 使用同一份数据库快照,只改变默认 Provider,列表随之切换到不同任务集合;显式查询全部 Provider 时,两组任务又同时出现。完整计数见故障复盘。
回到实际配置后,再往返切换顶层 model_provider,相同任务集合随之稳定出现和隐藏。数据库与 rollout 没有变化,配置成为唯一受控变量。
隔离实验给出相关性,实际环境中的可重复往返切换补齐因果链。
当前 App Server README 将未设置、null 和空数组描述为包含全部 Provider,公开仓库也有与实际行为差异相关的讨论。因此,涉及 modelProviders 的判断必须同时记录版本、请求参数和客户端入口。
项目映射与干净配置各能证明什么
任务只在某个项目中缺失时,项目映射当然值得检查。置顶、工作区提示、无项目集合和项目归属可以改变任务出现在哪一组,却未必改变列表是否返回任务。
调整项目映射后,如果任务能够换组但仍会在重启后消失,这个实验至少排除了“只修项目归属就能恢复完整历史”的假设。
干净配置则适合回答另一组问题:
- 新版本能否创建并持久化任务;
- 旧 rollout 能否被当前版本读取;
- 归档、项目和父子关系能否迁入;
- 故障是否会随旧配置重新出现。
先选择普通活动任务、归档任务、超长任务、有项目映射的任务和含工具调用的任务。代表性样本稳定后,再决定是否需要全量迁移。
别把历史分页当成正文截断
超长任务第一次打开时,客户端可能只展示最近几轮。此时先持续向前滚动,并观察更早内容是否逐步出现。
当前 App Server 还提供过实验性的分页接口:
thread/turns/list
thread/items/list
这些接口只对应特定分页模式,不能从界面现象反推出 Desktop 一定调用了它们。本机对照中,多次冷启动后继续向前加载,都能到达最早消息。原始 rollout 没有注入伪消息,也没有为了“唤醒历史”而改写正文。
页面只显示近期内容时,优先验证分页;只有分页始终无法到达、文件或解析证据也异常,才把问题升级到正文层。
一棵实用的诊断树
flowchart TD
A["历史任务不可见"] --> B["停止覆盖、清理与批量改写"]
B --> C["确认版本、CODEX_HOME 与 SQLite 位置"]
C --> D{"rollout 是否存在"}
D -->|"否"| E["查找旧路径与时间点快照"]
D -->|"是"| F{"SQLite 完整且有任务行"}
F -->|"否"| G["在隔离副本中评估元数据重建"]
F -->|"是"| H{"已知 ID 能否定位元数据"}
H -->|"否"| I["检查数据库、扫描与任务身份"]
H -->|"是"| P{"正文是否可读"}
P -->|"否"| Q["检查 includeTurns、路径与解析错误"]
P -->|"是"| J{"列表是否随配置变化"}
J -->|"是"| K["单变量定位 Provider 或查询条件"]
J -->|"否"| L["检查项目映射与客户端状态"]
K --> M{"只显示近期历史"}
L --> M
M -->|"是"| N["向前分页并做冷启动对照"]
M -->|"否"| O["检查恢复、认证、模型与传输"]
这棵树刻意把高风险修改放在后面。只要正文、元数据和路径仍然自洽,大多数前期判断都可以在副本中完成。
恢复以后怎样证明它真的好了
恢复验证至少覆盖:
| 检查 | 目的 |
|---|---|
| SQLite 完整性 | 排除结构损坏 |
| 任务 ID 集合 | 排除遗漏和意外新增 |
| 活动与归档数量 | 检查分类 |
rollout 与 session_meta.payload.id | 验证正文身份 |
| Provider 分布 | 识别可见性分区 |
| 项目、置顶和父子关系 | 检查组织结构 |
| 长任务向前分页 | 检查历史完整性 |
| 两次冷启动 | 排除单次缓存造成的假象 |
| 新消息写入位置 | 验证实际运行路径 |
如果这次工作还包含 CODEX_HOME 迁移,最后一项尤其重要。跨盘复制、多源合并和路径协调需要独立的迁移方案,不能只依靠列表恢复结果判断完成。
评论
由 GitHub Discussions 提供支持。