用 worktree 同时开两个 Claude Code 会话:各改各的,互不干扰
两个 Claude Code 会话同时改同一个仓库,文件会互相覆盖。加一个 --worktree 参数,每个会话就有自己的一份文件和自己的分支。本站实测了两个会话同时改同一个文件:各自提交、互不影响、最后合并无冲突。讲清它从哪个提交开始、依赖怎么办、退出时怎么清理。
本站已实测,测试日期:2026-10-11。
这篇教程解决一个问题:想让 Claude Code 同时做两件事,又不想让它们把同一个目录里的文件改乱。
Claude Code 干一件事要几分钟,等的时候再开一个会话做别的,很自然。但两个会话在同一个目录里,改的是同一批文件:一个在跑测试,另一个正好改了它依赖的代码,结果谁也说不清。
git 本来就有解决这个问题的东西,叫 worktree:同一个仓库的另一个工作目录,有自己的文件和自己的分支,和主目录共用同一份提交历史。Claude Code 把它包成了一个启动参数。
本站让两个会话同时改同一个测试文件,把整个过程走了一遍。
| 实测环境 | |
|---|---|
| 日期 | 2026-10-11 |
| 版本 | Claude Code 2.1.296,git 2.43 |
| 系统 | Ubuntu 24.04 |
| 模型 | Opus 5.5,auto 模式 |
| 连接方式 | 通过酷站AI 中转站,不是直连官方 |
| 示例仓库 | Express 的源码,v5.3.0,接着补测试和子代理那两篇 |
一个参数
在仓库目录里,启动时加 --worktree(或者 -w)和一个名字:
claude --worktree fix-redirect-test
启动后,顶部显示的目录变了:
▐▛███▛█ Claude Code v2.1.296
▝▜██████▀ Opus 5.5 · API Usage Billing
▝▝ ▝▝ ~/demo/b2-express/.claude/worktrees/fix-redirect-test
它做了两件事:在仓库的 .claude/worktrees/ 下建了一个以这个名字命名的目录,并在里面检出了一个新分支,分支名是 worktree- 加上你给的名字。用 git 自己的命令能看到:
$ git worktree list
/home/ccdemo/demo/b2-express 023032a [tests]
/home/ccdemo/demo/b2-express/.claude/worktrees/fix-redirect-test 023032a [worktree-fix-redirect-test] locked
不给名字也行,它会自己起一个。
官方文档提醒了一个前提:交互式地用 --worktree,要求这个目录已经被信任过。没在这里运行过 claude 的话,先运行一次、过了信任提示再用。
它从哪个提交开始
这是最容易想当然的地方。
官方的默认行为是从远程的默认分支开始(通常是 main),而不是从你当前所在的位置。这样新的 worktree 是一份和远程一致的干净代码。也就是说,你本地还没推送的提交、你正在做的功能分支,默认都不在新的 worktree 里。
我们这次的情况是个例外。上面的输出里,新 worktree 和主目录是同一个提交 023032a,那是我们本地刚提交、没有推送的。原因是我们的仓库是只克隆了一个标签的浅克隆,本地没有远程默认分支的记录。文档里写了这种情况:拿不到远程默认分支时,退回到当前的本地 HEAD。
如果你就是想从手头正在做的工作开始,官方文档给了一个设置,把 worktree.baseRef 设成 "head":
{
"worktree": {
"baseRef": "head"
}
}
它只接受 "fresh"(默认)和 "head" 两个值,不能填分支名。这个设置我们没有实测。
所以开工之前,先看一眼新 worktree 在哪个提交上,别假设。
实测:两个会话同时改同一个文件
子代理那篇里,评审员给 test/res.redirect.js 提了两条修改意见。我们把它们分给两个会话,同时做。
第一个终端:
claude --worktree fix-redirect-test
把 test/res.redirect.js 里“参数顺序写反”的那个弃用警告用例加上 .expect(500),跑这个测试文件确认通过,然后提交。
第二个终端,不等第一个做完:
claude --worktree stricter-asserts
把 test/res.redirect.js 里三个弃用警告用例的断言,从 assert.ok 加 indexOf 改成用 assert.deepStrictEqual
比对完整的警告列表。跑这个测试文件确认通过,然后提交。
两个会话改的是同一个文件,其中一个用例两边都动了。它们同时在跑,各用了一分钟多一点。第一个会话的记录:
● Update(test/res.redirect.js)
⎿ Added 1 line
120 request(app)
121 .get('/')
122 + .expect(500)
123 .end(function (err) {
● Bash(npx mocha --require test/support/env --reporter dot --check-leaks test/res.redirect.js 2>&1 | tail -20)
⎿ 16 passing (169ms)
● Bash(git add test/res.redirect.js && git commit -q -m "test: assert 500 when res.redirect arguments are reversed…)
⎿ a1a88f7 test: assert 500 when res.redirect arguments are reversed
都做完之后,看各个分支:
tests: 023032a test: cover append, redirect warnings and req.is with arrays; …
worktree-fix-redirect-test: a1a88f7 test: assert 500 when res.redirect arguments are reversed
worktree-stricter-asserts: 342661a test: assert full deprecation list in res.redirect invalid-argument tests
两个会话各自提交在自己的分支上,主目录所在的 tests 分支一动没动。谁也没有看到对方改了一半的文件。
一个小出入
第一个会话的总结里有一句:
提交:a1a88f7 test: assert 500 when res.redirect arguments are reversed,提交在 tests 分支上,没有推送。
分支说错了。提交实际在 worktree-fix-redirect-test 上,上面 git 的输出为证。它大概是把主目录的分支名当成了自己的。
这提醒一件事:在 worktree 里,分支、目录这类事实以 git 的输出为准,不要只看它的总结。
依赖怎么办
worktree 是一次全新的检出。node_modules、.env 这些不进 git 的东西,新目录里是没有的。我们确认过:
$ ls .claude/worktrees/fix-redirect-test/node_modules
ls: cannot access '.claude/worktrees/fix-redirect-test/node_modules': No such file or directory
那上面的测试为什么能跑?因为 worktree 的目录建在主目录里面,Node.js 找不到模块时会一层层往上级目录找,找到了主目录的 node_modules。被测的代码用的是 worktree 自己的那一份,测试工具借的是主目录的。
这是 Node.js 项目的巧合,不要指望它:
- 两边的依赖版本不同时(比如一个分支升级了某个包),借到的就是错的版本。
- 别的语言没有这种往上找的机制。Python 的虚拟环境、编译出来的产物,都要在新目录里重新准备。
官方文档给的做法是两条:
- 让 Claude 在 worktree 里装依赖,或者你自己到那个目录里跑一遍项目的初始化。
- 不进 git 的配置文件,用
.worktreeinclude自动带过去。 在项目根目录建这个文件,写法和.gitignore一样:
.env
.env.local
config/secrets.json
以后每次建 worktree,匹配到的文件会被自动复制进去。只有本来就被 git 忽略的文件才会复制,受版本控制的文件不会重复。这个文件我们没有实测。
退出时:留着还是删掉
在 worktree 的会话里退出,它会检查这个 worktree 里有没有东西。有提交的时候,出来的是这个:
Exiting worktree session
You have 1 commit on worktree-fix-redirect-test. The branch will be deleted if you remove the worktree.
❯ 1. Keep worktree Stays at /home/ccdemo/demo/b2-express/.claude/worktrees/fix-redirect-test
2. Remove worktree All changes and commits will be lost.
选第 2 项,目录、分支、里面的提交全都没了。 活还没合回去就要选第 1 项。
完整的规则,按官方文档:
| worktree 的状态 | 退出时 |
|---|---|
| 是干净的,会话没有起名字 | 自动删掉 worktree 和分支 |
| 是干净的,会话起了名字 | 先问你 |
| 里面有改动、未跟踪的文件或新提交 | 问你留还是删 |
用 claude -p 不带界面跑的 |
不清理,要自己用 git worktree remove 删 |
留下来的 worktree,以后可以用 claude --worktree 名字 --resume 回去接着做。
把活合回来
worktree 的分支就是普通的 git 分支,合并用的也是普通的 git 命令。回到主目录:
git merge worktree-fix-redirect-test
git merge worktree-stricter-asserts
第二次合并时两边改了同一个文件,git 自动合上了:
Auto-merging test/res.redirect.js
Merge made by the 'ort' strategy.
test/res.redirect.js | 12 +++++++++---
1 file changed, 9 insertions(+), 3 deletions(-)
合完跑测试:这个文件 16 个通过,全量 1267 个通过。
这次没有冲突,是因为两边改的行没有重叠。隔离只是让两个会话在干活的时候互不干扰,改到同一处的话,合并时该冲突还是会冲突。任务分得越开,合起来越省事。
用完清理:
git worktree remove .claude/worktrees/fix-redirect-test
git worktree remove .claude/worktrees/stricter-asserts
git branch -d worktree-fix-redirect-test worktree-stricter-asserts
另外,主目录里 git status 会把 .claude/worktrees/ 显示成未跟踪的目录。官方文档建议把它加进 .gitignore。
worktree 之间共用什么
各有各的文件和分支,但有几样是共用的。按官方文档:
| 共用的 | 意味着什么 |
|---|---|
仓库的 .git 目录 |
在 worktree 里提交,写的是同一个仓库 |
| 权限的批准 | 在一个 worktree 里选了“不再询问”,规则存到主目录的 .claude/settings.local.json,主目录和别的 worktree 都生效,删了这个 worktree 也还在 |
| 项目级安装的插件 | 不用每个 worktree 重装 |
hook 里的 $CLAUDE_PROJECT_DIR |
仍然指向启动会话时的项目根目录,不跟着进 worktree。hook 要知道 worktree 的路径,得从输入的 cwd 字段里读 |
最后一条容易出问题:你的 hook 脚本如果靠 $CLAUDE_PROJECT_DIR 去找被改的文件,在 worktree 里会找到主目录去。hook 的写法见Hooks 实战。
和别的并行方式怎么分
官方文档按三个问题来区分:
| 问题 | 对应的办法 |
|---|---|
| 谁来协调?Claude 在一个对话里派活、收结果 | 子代理 |
| 谁来协调?你自己交出几件独立的任务,回头再看 | 后台会话(agent view) |
| 任务会不会改到同一批文件? | 会的话,用 worktree 隔开 |
worktree 解决的只是最后一个问题。它可以和前两个叠着用:子代理的定义里写 isolation: worktree,这个子代理就在一个临时的 worktree 里干活。
这一趟花了多少
第一个会话 /cost 显示 0.22 美元,模型工作 1 分 1 秒;第二个没有单独记,规模相当。这是 Claude Code 按官方价格估算的数字,不是中转站实际扣的金额。
并行省的是你等待的时间,不是用量。两个会话同时跑,花的是两份。
这次实测没有覆盖到的
- 会话中途让它“在 worktree 里做” 没有测,只测了启动参数。
worktree.baseRef、.worktreeinclude、--resume回到 worktree 没有测,按官方文档写的。- 默认从远程默认分支开始的情况没有测到,我们的仓库是浅克隆,走的是退回本地 HEAD 的那条路。
- 没有遇到合并冲突。
- 子代理配合 worktree、桌面应用里的并行会话 没有测。
- 退出时选“保留”之后屏幕上打印的恢复命令,我们没有截到。