迁移 CODEX_HOME 看起来像一次普通的目录复制:把旧位置搬到新磁盘,再修改环境变量。但是,只要状态目录中还存在绝对路径,或迁移期间有两个目录先后承担过写入,这个问题就会迅速变成一场一致性工程。

大多数迁移只有一个可信数据来源,适合走单源复制。只有旧目录、临时目录和迁移半成品分别保存了独有数据时,才需要先进行多源合并。

“新目录可以启动”只完成了第一步;数据库、rollout 和迁移后的新消息全部收束到目标位置,旧目录不再承担运行时读写,迁移才真正闭环

本文接续历史任务不可见故障复盘任务存储与生命周期模型分层诊断方法,专门讨论跨盘迁移与多源合并。

公开环境变量与配置项以当前官方文档为准;state_5.sqlitethreads.rollout_path、具体索引文件和目录结构来自 0.145.0-alpha.18 的本机观察,均不属于稳定的数据迁移 API。

先确定你面对哪一种迁移

这里的“源”指可信数据来源的数量,而非目录数量。

单源迁移

一个有效 CODEX_HOME

目标卷 stage

新的 CODEX_HOME
多源合并

原活动目录 ─┐
临时运行目录 ├→ 统一 stage → 新的 CODEX_HOME
其他有效副本 ┘

如果当前只有一个目录保存最新数据库、正文和配置,就直接执行单源迁移。多个目录只有在各自包含独有且需要保留的状态时,才构成多源问题。

不要因为同时看见源目录和目标目录,就默认自己正在做多源合并

CODEX_HOME 之外还有 SQLite 位置

官方文档将 CODEX_HOME 定义为 Codex 的状态根目录,默认值为 ~/.codex。CLI、IDE extension、App Server 和安装器都会使用它,目录中可以包含配置、认证、日志、sessions、skills 与其他本地状态。

当前文档还提供:

CODEX_SQLITE_HOME

它控制 SQLite 状态目录,默认跟随 CODEX_HOME;配置项 sqlite_home 的优先级更高。于是,迁移前需要明确三个位置:

配置与 sessions 所在的 CODEX_HOME
SQLite 实际所在的目录
SQLite 中 rollout_path 指向的正文文件

如果 SQLite 位置使用相对路径,应先按启动进程的当前工作目录将它规范化为绝对路径,再进入盘点和迁移。否则,同一段配置可能因启动入口不同而落到不同位置。

CODEX_HOME 也不是完整的应用封装。程序本体、工作区、操作系统凭据和外部 MCP 服务可能仍在目录之外,需要在迁移后分别验证。

哪些数据值得迁移

本机版本中观察到的持久状态通常包括:

数据作用处理原则
sessions/活动任务 rollout原样复制并核对任务 ID
archived_sessions/归档任务 rollout原样复制并核对任务 ID
state_5.sqlite任务目录与元数据一致性快照、完整性检查、路径协调
session_index.jsonl部分任务索引随当前有效状态迁移
全局状态文件项目与部分界面状态防止旧快照覆盖更新状态
config.tomlProvider、MCP 与功能配置解析并保留稳定设置
.env代理等环境入口只迁移已验证配置
skills/rules/memories/用户持久能力按清单复制并校验

缓存、临时目录、日志数据库、模型缓存和插件运行缓存通常可以让目标版本重新生成。具体名称会随版本和启用功能变化,迁移前应盘点实际目录,再为每个排除项记录理由。

认证可能同时依赖状态目录文件、操作系统安全存储和应用登录状态。迁移后重新登录属于可接受结果,不应为了避免登录而复制来源不明的凭证。

先定义迁移闭环

一个可验证的完成标准是:

Codex 从新 CODEX_HOME 启动

SQLite 位于计划中的状态目录

rollout_path 指向新目录

对应 rollout 确实存在

迁移后的测试消息追加到新 rollout

旧目录停止承担读写

环境变量、文件数量和应用能否启动,都只能覆盖这条链中的一部分。

单源迁移:让所有修改发生在 stage

推荐流程为:

盘点 → 停机快照 → 准备 stage → 静态验证
     → 激活 → 运行验证 → 回滚窗口 → 清理

在目标卷创建隔离 stage,可以把复制、路径协调和完整性检查与活动源目录分开。验证通过后,再通过同卷目录改名缩短激活窗口。

盘点与停机

先记录:

  • Codex 版本;
  • 当前 CODEX_HOMECODEX_SQLITE_HOMEsqlite_home
  • 用户级、机器级和当前进程中的环境变量;
  • 源目录大小与目标卷空间;
  • 活动和归档 rollout 数量;
  • SQLite、配置、Skills、MCP 与 Memories 的实际位置;
  • 准备排除的缓存目录。

Windows 用户级环境变量只影响之后启动的进程。当前 PowerShell 中的值正确,不能证明已经运行的 Codex 使用同一目录。

正式复制前,完整退出 Desktop 窗口、系统托盘和相关后台进程,再建立只读快照。运行中复制可能把 SQLite、WAL 和活动 rollout 带到不同时间点。

准备 stage 与 manifest

stage 应与最终目录位于同一卷,并与活动源目录完全隔离。准备过程中:

  1. 复制持久状态;
  2. 排除已确认可重建的缓存;
  3. 保留源目录结构;
  4. 生成文件 manifest;
  5. 记录排除项及理由;
  6. 不修改活动源目录。

manifest 至少记录相对路径、长度和修改时间。SQLite、JSONL、TOML 与用户资源适合附加 SHA-256。

stage 的意义,是让路径改写、数据库检查和失败重来都发生在副本中,源目录始终保留为回滚依据

确认并处理 rollout_path

本机 0.145.0-alpha.18threads.rollout_path 保存绝对路径。复制后可能形成:

数据库已经位于新目录
rollout_path 仍指向旧目录
新消息继续追加到旧 rollout

先在数据库副本中做只读检查:

PRAGMA integrity_check;

SELECT id, rollout_path
FROM threads;

若路径仍包含旧根目录,优先在一次性 stage 中测试当前版本支持的扫描、发现或元数据重建路径,并验证它能否从已复制的 rollout 恢复所需关系。内部 SQLite 没有公开稳定的迁移契约,直接改写只能作为专家级最后手段。

确实需要改写数据库副本时,必须先确认当前 Schema、保留原始快照、使用事务与可审计脚本,并且只处理规范化后真正位于旧目录边界内的路径。简单字符串替换可能误伤名称相近的其他目录。操作完成后,再次检查数据库完整性、路径边界、文件存在性以及 session_meta.payload.id;任何无法解释的差异都应终止激活。

静态验证

激活前至少检查:

检查目标
SQLite 完整性PRAGMA integrity_check 返回 ok
任务 ID 集合与迁移计划一致
rollout 存在每条数据库路径都指向实际文件
rollout 身份session_meta.payload.id 与任务 ID 匹配
路径边界全部位于目标根目录
旧路径残留数据库与配置不再依赖旧根目录
TOML 解析config.toml 语法有效
持久资源与 manifest 一致

带着已知错误进入激活,只会把可控的 stage 问题变成活动环境故障。

激活与运行验证

再次确认 Codex 完全退出后:

  1. 如果最终目录已经存在,将它同卷改名为带时间戳的 holding;
  2. 将已验证 stage 同卷改名为最终目录;
  3. 记录 holding → 原最终目录 的完整回滚顺序;
  4. 设置用户级 CODEX_HOME,必要时同时设置 SQLite 位置;
  5. 关闭仍保留旧变量的终端;
  6. 从正常桌面入口重新启动 Codex。

启动后检查任务、归档、项目、Skills、MCP、Memories、配置和登录状态。随后发送一条可识别的测试消息,并观察目标 rollout 是否增长、旧目录是否停止产生新写入。

新消息落在哪里,是迁移运行路径最直接的终局证据

运行验证失败时,先退出 Codex,再恢复旧环境变量与激活前目录。失败的目标目录应改名保留,作为新的调查现场。

多源合并:先按数据层选择权威来源

如果旧规范目录保存完整任务树,临时目录保存更新配置,而某些活动 rollout 仍在另一位置继续增长,就需要先形成一个自洽的统一 stage。

正式合并前,冻结并快照每个候选来源,分别记录:

  • 根目录与最后使用时间;
  • 数据库完整性;
  • 活动与归档任务数量;
  • rollout 文件清单;
  • 配置和持久资源清单;
  • 是否仍被绝对路径引用;
  • 是否曾作为活动状态目录。

文件修改时间只能辅助判断。数据库、正文和配置可能分别在不同目录中继续变化,目录级时间戳无法替代逐层证据。

每类数据分别选择

数据层选择原则
rollout保留正文完整、事件追加最靠后的有效副本
SQLite选择结构完整、任务关系齐全的数据库作基底
索引与全局状态选择项目和界面组织更完整的副本
config.toml以稳定配置为基底,逐项合并独有定义
Skills、rules、memories按相对路径比较并保留独有内容
缓存排除,让目标版本重新生成

多源合并没有必要选出一个“所有内容都最新”的目录。权威来源可以随数据层变化,但每个选择都要进入合并 manifest。

给重复 rollout 分类

同一任务 ID 的多个副本可以分成:

类型判断处理
完全相同长度与哈希一致任取一份并记录来源
严格追加短文件是长文件的完整前缀保留长文件
单侧独有只在一个来源出现迁入并验证身份
内容分叉前缀不同或身份冲突停止自动合并
无法确认文件损坏或 ID 不匹配隔离保留

严格追加需要同时满足:

len(长文件) >= len(短文件)

SHA256(短文件)
==
SHA256(长文件前 len(短文件) 个字节)

还要确认两份 rollout 的 session_meta.payload.id 相同、JSONL 均可解析、长文件尾部事件完整。

前缀关系不成立时,不要把两份 JSONL 直接拼接;它们可能已经在两个目录中分别继续写入

选择 SQLite 基底

数据库基底依次考虑完整性、任务 ID 集合、归档与项目关系、父子任务结构,以及所有 rollout_path 能否映射到已经选定的正文。

当前没有公开稳定的多源 SQLite 合并契约。优先保留一份完整基底,再在隔离 stage 中评估当前版本支持的 rollout 扫描或元数据重建能力。无法证明关系可以完整恢复时,应让来源继续隔离保存,等待人工审查;直接迁入内部表只适合充分理解当前 Schema、能够逐项验证并随时回滚的专家操作。

不要将运行中的 WAL 与另一时点的主数据库任意组合,也不要为了统一列表而批量改写历史 Provider。

配置适合逐项合并:保留稳定主体,加入另一来源独有的 MCP、Provider 或功能定义,检查重复键并重新解析 TOML。默认 Provider 应在迁移前后保持有记录、可回滚,除非迁移本身明确要求切换。

多源协调结束后,统一 stage 应具备:

一份可解析的 config.toml
一份结构完整的任务 SQLite
完整且不重复的 rollout 集合
全部绝对路径指向目标
持久资源来源可追溯
缓存与未知瞬时状态已排除

从这里开始,它就重新变成单源迁移,可以继续执行静态验证、激活、运行验证与回滚窗口。

Windows 上容易混淆的两个问题

数据库中的路径可能使用:

\\?\<目标 CODEX_HOME>\sessions\...

\\?\ 是 Windows 扩展长度路径前缀。规范化后仍可能属于同一个本地目标目录,不能仅凭前缀判断为旧路径或网络路径。

删除 DLL 或 SQLite 时出现 Access denied,通常意味着进程、插件或安全软件仍持有文件。它与路径过长属于不同问题。遇到占用时应停止激活或清理、保留现场并确认进程状态,不要跳过来源不明的持久文件。

什么时候可以清理旧目录

一次成功启动不足以支持立即删除源目录。至少等到:

多次冷启动正常

任务与归档抽查通过

测试消息持续写入目标目录

没有数据库或配置引用旧根目录

回滚快照已经保留

复制、验证、激活、运行确认和清理应当分阶段完成。清理失败只能说明文件仍被占用或删除条件尚未满足,不能反过来推翻已经完成的迁移验证。

一份精简检查清单

单源迁移

  • 记录版本、状态根目录和 SQLite 位置
  • 确认只有一个可信数据来源
  • 完全退出 Codex 并建立快照
  • 在目标卷准备 stage 与 manifest
  • 检查 SQLite 完整性和任务 ID
  • 协调并验证绝对 rollout_path
  • 解析配置并检查持久资源
  • 激活目标目录与环境变量
  • 发送测试消息验证真实写入位置
  • 完成多次冷启动并保留回滚窗口

多源合并

  • 冻结并快照全部来源
  • 按数据层选择权威来源
  • 为重复文件计算长度与哈希
  • 验证重复 rollout 的身份与严格前缀关系
  • 隔离分叉和无法确认的数据
  • 选择完整的 SQLite 基底
  • 逐项合并配置与持久资源
  • 形成单一、自洽、可追溯的 stage
  • 回到单源迁移的验证与激活流程

迁移的核心并不复杂:保护源数据,让所有修改先发生在副本中,再用可重复的检查证明目标目录已经接管运行。真正需要耐心的地方,是拒绝用“看起来正常”替代完整的证据闭环。

参考资料