先分清三种“分支”
官方术语里只有 Handoff 和 Thread fork 被明确定义过。日常说的“开分支”通常指的是第三件事:在 Git 层开一条 branch。所以先把三层对齐,后面就不会互相误解。
| 层 | 解决什么问题 | 官方叫法 | 在哪儿用 |
|---|---|---|---|
| 运行环境 | 这次任务在哪份文件树里跑,会不会弄脏你的工作区 | Local / Worktree / Cloud | 桌面端新建对话时选 |
| 搬移 | 已有的对话和它的代码怎么换地方继续 | Handoff | 对话底部的运行位置入口 |
| 会话历史 | 从一条已有对话的历史里开出新对话,两条各走各的 | Thread fork | app-server 协议、SDK |
| Git 分支 | 代码层面的分叉与合并 | Branch | git 本身,Codex 不替你发明语义 |
三种运行环境怎么选
新建对话时可以选 Work in:这台电脑,或者云端。选这台电脑时再选 Local 还是 Worktree。这三个选项决定了改动落在哪儿。
| 环境 | 改动落在哪 | 适合什么 | 注意 |
|---|---|---|---|
| Local | 你当前的项目目录,直接改 | 单线推进、想随时手工接管、改完立刻跑 | 会和你的未提交改动混在一起 |
| Worktree | 同一仓库的另一份检出,本机运行 | 并行试两个方案、排队跑后台任务、先隔离再合并 | 需要 Git 仓库;默认 detached HEAD |
| Cloud | 云端任务自己的工作区,基于已发布的环境 | 长时间无人值守、需要预装依赖或联网凭据、要批量跑 | 环境要先发布;比本地更费额度 |
Cloud 的关键差别是“环境”:依赖装什么、能连哪些服务、跑在什么基础镜像上,都要提前配置并发布,之后多个任务复用同一套环境。代价是配置有前置成本,好处是任务之间一致。另外云端任务有自己的一份工作文件,你本机的未提交改动、后台进程、浏览器登录态都不会跟过去。
用 Worktree 并行:一次完整操作
- 01
1. 确认这是 Git 仓库
不是 Git 仓库时 Worktree 选项没有意义。先 git init 或确认 clone 完整,工作区最好没有一半改一半没改的状态。
- 02
2. 在输入框里选 Worktree
新建对话时把运行环境选成 Worktree。如果项目配了本地环境的初始化脚本,可以在这里选一个,让新 worktree 自动装依赖。
- 03
3. 选起始分支
可以基于某个分支,也可以基于当前分支(包括你尚未提交的改动)。Codex 在 worktree 里默认以 detached HEAD 工作,目的是不占用分支名。
- 04
4. 提交任务,等它跑
这条对话此后就绑定在这个 worktree 上。你可以同时开第二条对话、选另一个 Worktree,两条并行改不同方向,互不污染。
- 05
5. 收口
满意了就把改动搬回 Local(Handoff),或者直接在 worktree 里建分支、提交、开 PR。不满意就丢掉这个 worktree,成本很低。
# 手工开一份 worktree,和 Codex 托管的那份互不影响
git worktree add ../feature-a -b feature/a
git worktree add ../feature-b -b feature/b
# 看看现在有哪些 worktree,以及各自占着哪个分支
git worktree list
# 用完清理(先确认里面没有未保存的东西)
git worktree remove ../feature-agitignore 掉的文件怎么办
worktree 是新的一份检出,你的 .env、本地配置、证书这些被 gitignore 的文件不会自动出现。Codex 给了一个机制:在仓库根放一个 .worktreeinclude,列出要复制进去的路径。
# 仓库根目录新建 .worktreeinclude
cat > .worktreeinclude <<'EOF'
.env
.env.local
config/secrets.json
EOF
# 规则要点:
# 1. 只复制被 gitignore 的文件,已被跟踪的文件本来就在检出里
# 2. 跳过符号链接,已存在的不覆盖
# 3. 只对桌面端本机托管的 worktree 生效Handoff:把对话和代码搬到别处继续
Handoff 解决的是“我已经在 Local 里聊了一半,现在想让它到隔离环境里继续”,或者反过来“云端/远程跑完了,我要在本机接着改”。它搬两样东西:对话本身,以及它的 Git 状态。
| 从 | 到 | 支持吗 | 说明 |
|---|---|---|---|
| Local | Worktree | 支持 | Codex 会建或复用目标 worktree,把对话切过去 |
| Worktree | Local | 支持 | 把改动带回你原来的检出,最常见的收口路径 |
| 本机 | 远程主机 | 支持 | 目标主机上必须已有同一个仓库的项目,且分支要对得上 |
| 远程主机 | 本机 | 支持 | 在运行位置里选回这台电脑即可 |
| 任意 | Cloud 环境 | 不支持 | 云端任务从环境发布开始,不能从本地对话直接搬过去 |
- 01
1. 打开要搬的那条对话
注意:你没法让一条正在运行的对话把自己搬走。如果它正在生成,先停下,或者换一条对话来发起这次搬移。
- 02
2. 点底部的运行位置
那里显示当前在哪台机器上跑。点开就是目标列表,本机与已连接的远程主机都在里面。
- 03
3. 选目标并确认分支
Codex 会显示目标位置和将要使用的分支。远程主机上只列出匹配同一仓库的已保存项目,找不到就说明那边还没建项目。
- 04
4. 确认 Handoff
它会在目标位置建或复用 worktree,把对话和 Git 状态传过去。如果有响应正在生成,这一步会中断它。
命令行里直接分叉:codex fork
如果你在终端里用 CLI,分叉不需要动文件系统,也不需要重开一条空白对话——它是一个独立子命令。codex fork 会复制一条已有会话的历史,生成新的 session id,之后两条会话各自独立增长。原会话不会被改动。
# 不带参数:弹出会话选择器,手动挑一条来分叉
codex fork
# 直接分叉最近一条会话,跳过选择器
codex fork --last
# 已知 session id 时精确指定,并可带一句开场提示词
codex fork 0f3c1a2e-8b6d-4a51-9c7e-2d4b8f1a6e30 "从断点继续,先跑一遍测试"- fork 出来的是新会话,历史被复制过去,但两边后续各写各的,互不影响
- 需要接着原会话往下跑而不是分叉时,用 codex resume(--last 续跑最近一条)
- 分叉只复制对话历史,不复制工作区的 Git 状态;代码层面的隔离仍然靠 worktree
接口层:thread fork 才是真正的“复制历史”
如果你在把 Codex 嵌进自己的工具里,分支就不该靠文件系统了,而是靠 app-server 的 thread 接口。thread fork 的含义是:从一条已有 thread 的存储历史里复制出一份,变成一个独立的 thread id,之后两边各走各的。
// 请求:把 thr_123 的历史复制成一条新 thread
{
"id": 12,
"method": "thread/fork",
"params": {
"threadId": "thr_123",
"lastTurnId": "turn_45",
"ephemeral": false
}
}
// 响应:新 thread 保留来源信息,sessionId 指向源 thread
{
"id": 12,
"result": {
"thread": {
"id": "thr_456",
"sessionId": "thr_123",
"forkedFromId": "thr_123"
}
}
}- lastTurnId:只复制到指定那一轮为止,后面的轮次不带过去,适合“从某个好的状态重新试一次”
- excludeTurns:排除指定的若干轮,适合把一次跑偏的尝试从历史里剪掉
- ephemeral 设为 true:只在内存里分叉,不落盘,适合对照实验或临时重试;对分页的 thread 做临时分叉时必须同时用 excludeTurns
- 源 thread 正好在一轮中间时,fork 会记一条中断标记,而不是等它跑完
- 分叉出来的 thread 会触发一次 thread/started 事件,事件里的 id 才是你后续要继续用的那个
import { Codex } from "@openai/codex-sdk";
const codex = new Codex();
// 继续一条已有的 thread(本地执行)
const thread = await codex.resumeThread("thr_456");
const first = await thread.run("先看一遍这个仓库的测试策略");
// 再调用一次 run,就是在同一条 thread 上继续,不是新开对话
const second = await thread.run("按刚才的策略补上缺失的用例");
console.log(second.finalResponse);常见坑与验收清单
| 现象 | 原因 | 怎么办 |
|---|---|---|
| 提示分支已被某个 worktree 占用 | git 不允许同一个分支在两处同时检出 | 在目标 worktree 里换一个分支名,或者先把手上的 worktree 收掉 |
| worktree 里改了半天,回 Local 看不到 | worktree 是独立检出,改动不会自动回来 | 用 Handoff 搬回 Local,或在 worktree 里提交后合并 |
| 以为能从分享快照接着聊 | 线程快照是只读的,不能从快照 fork 回原对话 | 把快照当附件导入一条新对话参考,历史不会接上 |
| Handoff 时正在生成的回复没了 | 搬移会中断当前响应 | 先等它跑完,或者换一条对话发起搬移 |
| 本地对话搬不到云端 | handoff 到云端环境不支持 | 云端任务从发布好的环境新建,用 AGENTS.md 和文件把上下文带过去 |
| 想从断点开分支但找不到开关 | 分叉不在对话界面里,是一个独立子命令 | 用 codex fork:默认弹出会话选择器,codex fork --last 直接分叉最近一条,也可传 session id |
- 并行前把任务切成互不重叠的分支,一次只让一条对话动一份工作区
- 每个 worktree 都从干净的起点出发,未提交的改动先 commit 或 stash
- 把仓库特有约定写进 AGENTS.md,让每条分支上的对话看到同样的规则
- 改完用 review 或 PR 收口,不要靠人肉 diff 记着哪条对话改了什么
- 用完清理 worktree,别让几十份检出把磁盘和分支名都占住
- 涉及密钥的并行任务,先想清楚哪些文件该被复制进 worktree
常见问题
并行开十条对话会更快吗?
通常不会。瓶颈几乎总在你自己:审阅改动、合并分支、处理冲突都要串行。实践里两到四条并行比较常见,再多就要靠更小的任务切分和更严的 CI 才能撑住。
Worktree 和直接复制一份仓库有什么区别?
worktree 共享同一个 .git 目录,分支、提交、远程都是一套,所以切换和合并成本低,也不会因为复制出一堆独立仓库而失去同步。复制目录则更像两个互不相干的仓库,最后合并会麻烦很多。
ephemeral 的 fork 值得用吗?
在需要反复对照、又不想污染历史时值得用,比如同一份上下文试三种提示词。它不落盘,所以进程结束就没了,不适合要留档的实验。另外对分页过的 thread 做临时分叉时必须显式排除若干轮,这不是可选项。
从快照看到别人的对话,能不能直接接着改?
不能。快照是只读视图,官方明确它不能用来获取原项目的访问权,也不能从快照 fork 回原对话。你只能把它当附件导入一条新对话当参考,历史与工具状态不会跟过来。
官方文档与参考
本文依据上述官方文档整理;价格、额度与功能会随官方更新变化,第三方中转站的行为以其自身说明为准。