迁移 CODEX_HOME 看起来像一次普通的目录复制:把旧位置搬到新磁盘,再修改环境变量。但是,只要状态目录中还存在绝对路径,或迁移期间有两个目录先后承担过写入,这个问题就会迅速变成一场一致性工程。
大多数迁移只有一个可信数据来源,适合走单源复制。只有旧目录、临时目录和迁移半成品分别保存了独有数据时,才需要先进行多源合并。
“新目录可以启动”只完成了第一步;数据库、rollout 和迁移后的新消息全部收束到目标位置,旧目录不再承担运行时读写,迁移才真正闭环。
本文接续历史任务不可见故障复盘、任务存储与生命周期模型和分层诊断方法,专门讨论跨盘迁移与多源合并。
公开环境变量与配置项以当前官方文档为准;state_5.sqlite、threads.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.toml | Provider、MCP 与功能配置 | 解析并保留稳定设置 |
.env | 代理等环境入口 | 只迁移已验证配置 |
skills/、rules/、memories/ | 用户持久能力 | 按清单复制并校验 |
缓存、临时目录、日志数据库、模型缓存和插件运行缓存通常可以让目标版本重新生成。具体名称会随版本和启用功能变化,迁移前应盘点实际目录,再为每个排除项记录理由。
认证可能同时依赖状态目录文件、操作系统安全存储和应用登录状态。迁移后重新登录属于可接受结果,不应为了避免登录而复制来源不明的凭证。
先定义迁移闭环
一个可验证的完成标准是:
Codex 从新 CODEX_HOME 启动
↓
SQLite 位于计划中的状态目录
↓
rollout_path 指向新目录
↓
对应 rollout 确实存在
↓
迁移后的测试消息追加到新 rollout
↓
旧目录停止承担读写
环境变量、文件数量和应用能否启动,都只能覆盖这条链中的一部分。
单源迁移:让所有修改发生在 stage
推荐流程为:
盘点 → 停机快照 → 准备 stage → 静态验证
→ 激活 → 运行验证 → 回滚窗口 → 清理
在目标卷创建隔离 stage,可以把复制、路径协调和完整性检查与活动源目录分开。验证通过后,再通过同卷目录改名缩短激活窗口。
盘点与停机
先记录:
- Codex 版本;
- 当前
CODEX_HOME、CODEX_SQLITE_HOME和sqlite_home; - 用户级、机器级和当前进程中的环境变量;
- 源目录大小与目标卷空间;
- 活动和归档 rollout 数量;
- SQLite、配置、Skills、MCP 与 Memories 的实际位置;
- 准备排除的缓存目录。
Windows 用户级环境变量只影响之后启动的进程。当前 PowerShell 中的值正确,不能证明已经运行的 Codex 使用同一目录。
正式复制前,完整退出 Desktop 窗口、系统托盘和相关后台进程,再建立只读快照。运行中复制可能把 SQLite、WAL 和活动 rollout 带到不同时间点。
准备 stage 与 manifest
stage 应与最终目录位于同一卷,并与活动源目录完全隔离。准备过程中:
- 复制持久状态;
- 排除已确认可重建的缓存;
- 保留源目录结构;
- 生成文件 manifest;
- 记录排除项及理由;
- 不修改活动源目录。
manifest 至少记录相对路径、长度和修改时间。SQLite、JSONL、TOML 与用户资源适合附加 SHA-256。
stage 的意义,是让路径改写、数据库检查和失败重来都发生在副本中,源目录始终保留为回滚依据。
确认并处理 rollout_path
本机 0.145.0-alpha.18 的 threads.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 完全退出后:
- 如果最终目录已经存在,将它同卷改名为带时间戳的 holding;
- 将已验证 stage 同卷改名为最终目录;
- 记录
holding → 原最终目录的完整回滚顺序; - 设置用户级
CODEX_HOME,必要时同时设置 SQLite 位置; - 关闭仍保留旧变量的终端;
- 从正常桌面入口重新启动 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
- 回到单源迁移的验证与激活流程
迁移的核心并不复杂:保护源数据,让所有修改先发生在副本中,再用可重复的检查证明目标目录已经接管运行。真正需要耐心的地方,是拒绝用“看起来正常”替代完整的证据闭环。
评论
由 GitHub Discussions 提供支持。