教程Claude Code系列第 4 / 8 篇

用 worktree 同时开两个 Claude Code 会话:各改各的,互不干扰

两个 Claude Code 会话同时改同一个仓库,文件会互相覆盖。加一个 --worktree 参数,每个会话就有自己的一份文件和自己的分支。本站实测了两个会话同时改同一个文件:各自提交、互不影响、最后合并无冲突。讲清它从哪个提交开始、依赖怎么办、退出时怎么清理。

发布 · 约 8 分钟读完 · 经人工审核()· 本站的内容怎么来的

本站已实测,测试日期:2026-10-11。

本页目录(10 节)
  1. 一个参数
  2. 它从哪个提交开始
  3. 实测:两个会话同时改同一个文件
  4. 依赖怎么办
  5. 退出时:留着还是删掉
  6. 把活合回来
  7. worktree 之间共用什么
  8. 和别的并行方式怎么分
  9. 这一趟花了多少
  10. 这次实测没有覆盖到的

这篇教程解决一个问题:想让 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、桌面应用里的并行会话 没有测。
  • 退出时选“保留”之后屏幕上打印的恢复命令,我们没有截到。

来源

通过 API 中转站使用这些模型?到 Veridrop 免费检测中转站给你的是不是它声称的模型,或者看看中转站排行榜前 100 名。

相关文章