Codex Desktop 更新后项目为空,但聊天记录还在:一次线程归属迁移失败排查记录

Codex Desktop 更新后项目为空,但聊天记录还在:一次线程归属迁移失败排查记录
沧浪同学免责声明
本文只记录一次 Codex Desktop 本地数据迁移故障的排查思路。不同版本的数据库结构、接口名称和字段含义可能变化,操作前请先完全退出客户端并备份相关文件。不要把聊天正文、账号信息、令牌或完整数据库上传到公开位置。
Codex Desktop 更新后项目为空,但聊天记录还在:一次线程归属迁移失败排查记录
最近一次更新后,Codex Desktop 出现了一个很容易误判的问题:侧边栏的“项目”变成空白,但“最近”还能够显示,搜索也能找到以前的聊天。
这说明一个重要事实:聊天记录并没有消失,出问题的是聊天和项目之间的索引关系。
我最初也参考了之前类似问题的处理方法,尝试检查 roles(职业标签)和 workMode(工作模式)。但这次修改没有解决问题。继续往下查之后,才发现这不是工作模式过滤,而是一次没有完成的项目迁移。
一、先区分“数据消失”和“数据不显示”
遇到侧边栏空白,第一反应往往是数据库损坏,或者更新时把聊天删掉了。实际上,几个现象可以帮助我们先做区分:
| 现象 | 更可能说明什么 |
|---|---|
| 搜索仍能找到旧聊天 | 聊天主体数据仍然存在 |
| “最近”仍能显示 | 全局线程索引仍可读取 |
| 只有“项目”为空 | 项目过滤或线程归属关系可能异常 |
| 重启后现象不变 | 不只是前端暂时没有刷新 |
本次四个现象同时出现,因此排查重点不应放在聊天正文,而应该放在项目索引和线程归属上。
二、旧方案为什么没有效果
之前类似故障的解决方案,是把全局状态里的下面两个字段改成空值:
1 | { |
这套方法针对的是另一类问题:客户端根据首次引导中的职业标签,自动切换到了不显示本地会话的侧边栏模式。那时,项目和聊天数据都可能正常,只是被工作模式过滤掉了。
但这次检查发现,客户端的项目数据已经存在。即使清空 roles 和 workMode,也不会凭空生成线程到项目的归属关系,所以侧边栏仍然没有内容。
换句话说,旧方案处理的是“显示权限”,这次坏掉的是“数据关系”。 两者表面上都表现为项目为空,但修复位置完全不同。
三、真正的原因:项目迁移完成,线程归属迁移没有完成
在本地数据中可以看到三组关键事实:
- 项目表仍有 74 个项目记录;
- 线程表仍有 1040 条聊天线程;
- 原本应该指向项目的
project_id全部为空。
全局状态里的迁移标记也提供了旁证:
1 | projectsMigrated=true |
这个状态组合很关键。它表示客户端已经完成了项目记录的迁移,却没有完成“把聊天线程分配给对应项目”这一步。项目本身还在,聊天也还在,只是中间那条关系没有建立起来。
因此,这次故障可以准确地概括为:
项目索引存在,线程索引存在,但线程到项目的归属索引断了。
四、排查时如何确认线程属于哪个项目
线程通常会保存一个工作目录字段,也就是 cwd(当前工作目录)。项目则有自己的根目录。只要把这两个目录进行规范化后比较,就可以判断线程是否属于某个项目。
这里有两个容易忽略的细节。
1. Windows 路径前缀不能直接当成不同目录
同一个目录可能以普通路径出现,也可能带有 \\?\\ 前缀。比较前需要先去掉这个前缀,并统一大小写、路径分隔符和末尾斜杠,否则会把同一个项目误判成两个目录。
2. 项目判断不只是字符串相等
线程的工作目录可能是项目根目录下面的子目录。因此,除了判断“是否完全相同”,还需要判断线程目录是否位于项目根目录之下,并确保边界是完整的路径段,不能把 project-a 错配到 project-ab。
按照这套规则复核后,1040 条线程中有 906 条可以唯一匹配到 73 个项目;另外 134 条没有找到对应的项目根目录。这些线程应当保留为项目外聊天,不能为了让侧边栏看起来完整而强行归类。
五、实际修复方式
这次修复没有直接修改 SQLite 表,而是使用当前版本支持的线程元数据更新接口,逐条写入正确的 projectId。这样做的原因是,客户端除了数据库字段外,可能还维护缓存、事件记录或其他派生索引,直接改表容易造成表面恢复、重启后又被覆盖。
实际操作可以按下面的顺序进行:
第一步:完全退出 Codex Desktop
关闭窗口后,还要确认托盘和任务管理器中没有残留进程。客户端仍在运行时修改本地状态,退出过程可能把旧状态再次写回。
第二步:备份全局状态和本地数据库
至少备份全局状态文件和保存项目、线程数据的 SQLite 文件。备份目录应带上日期时间,方便在出现异常时恢复。
第三步:建立线程到项目的匹配表
读取项目根目录和线程 cwd,完成路径规范化后,只保留唯一匹配结果。无法匹配的线程不要修改。
第四步:通过客户端支持的元数据接口写入归属
对每个唯一匹配的线程写入对应 projectId,而不是批量替换数据库内容。写入后重新读取线程元数据,确认字段已经保存。
第五步:重启并验证三个结果
重启客户端后,至少检查:
- “项目”数量是否恢复;
- 项目内是否出现原来的聊天;
- 没有项目归属的聊天是否仍能在“最近”或搜索中找到。
这次修复后,906 条线程恢复到项目中,134 条仍作为项目外聊天保留,聊天正文、授权、插件和其他配置没有改动。
六、补充问题:SSH 连接入口为什么也不见了
项目恢复后,我又发现原来连接甲骨文服务器的 SSH 入口没有出现在客户端里。继续检查后确认,服务器本身没有故障,私钥也没有丢失:显式指定端口和私钥时,SSH 可以正常登录,远端 codex(Codex 命令行)也能运行。
问题出在本机 ~/.ssh/config(SSH 配置文件)。当时的配置只记录了主机、端口和用户,没有记录 IdentityFile(私钥路径)。因此出现了一个很有代表性的差异:
1 | 显式指定私钥:连接成功 |
按照 OpenAI 官方远程连接说明,当前版本会从 SSH 配置中发现具体主机别名,再交给 OpenSSH(开放式安全外壳协议实现)解析连接参数。客户端不应依赖一条只存在于旧版内部状态里的连接记录。
补全后的结构如下,路径需替换为自己的实际私钥位置:
1 | Host 服务器地址或别名 |
补上 IdentityFile 后,直接执行 ssh 主机 已能无交互登录,说明客户端自动连接所需的基础配置已经恢复。最后还需要在 Codex Desktop 中打开“设置 → 连接 → SSH”,添加或启用这台主机,再选择远端项目目录。
这部分无法通过修改本地项目数据库代替,因为新增或启用连接属于客户端设置流程。强行伪造内部缓存,反而可能造成新旧版本状态继续不一致。
远端数据也做了只读核对:服务器上仍有 218 个会话文件、4 个归档会话,历史数据库约 217 MB。SSH 入口消失,不代表远端聊天被删除。 重新连接同一台服务器和同一个用户后,旧聊天仍有恢复和读取的数据基础。
这里还有一个和项目迁移相似的信号:本地状态已经写入 remote-project-connection-backfill-completed=true(远程项目连接回填已完成),但项目元数据中没有远程连接记录。也就是说,迁移标记认为工作已经结束,实际连接关系却没有完成回填。
七、这类故障最容易踩的坑
不要一看到项目为空就清理全部配置
清理全局状态、删除数据库或重新登录,都会扩大问题范围。只要搜索还能找到聊天,就应该先证明数据是否仍在。
不要把迁移标记当成“全部迁移完成”
一个总的迁移状态,可能拆成项目迁移、线程归属迁移、缓存重建等多个阶段。看到 projectsMigrated=true,不能据此推断线程已经正确归类。
不要为了填满项目而强行匹配
工作目录可能来自临时目录、已经删除的项目,或者另一台机器。错误归类会让后续检索更加混乱,比项目暂时为空更难排查。
八、最后的结论
这次 Codex Desktop 更新后的异常,不是聊天记录被删除,也不是旧方案里的职业标签或工作模式导致的过滤。
真正原因是:新版迁移已经创建或保留了项目记录,但线程到项目的归属迁移没有完成,导致 project_id 全部为空;远程连接迁移也留下了“标记完成、关系缺失”的相似状态。
这也是为什么“最近”还在、搜索还能用,而“项目”却像被清空了一样。表面上看是侧边栏问题,本质上是一次数据关系迁移中断。
软件升级最容易破坏的,往往不是主体数据,而是主体数据之间的索引、映射和归属关系。判断问题时,先问一句“数据真的没了吗,还是只是关系断了”,通常比直接重置配置更接近答案。









