12k
All articles

在 AI 编码 Agent 中使用 Git Worktrees

使用 git worktrees 配合 AI 编码代理,隔离分支、避免文件冲突,并安全管理端口、数据库和合并。

OpenReplay Team
OpenReplay Team
在 AI 编码 Agent 中使用 Git Worktrees

在同一个仓库上可靠地运行多个 AI 编码 agent,方法是遵循「一个任务、一个分支、一个 worktree、一个 agent」:每个 agent 拥有通过 git worktree add 创建的独立目录,在自己的分支上工作,绝不触碰其他 agent 正在编辑的文件。

大多数开发者都是吃过苦头才总结出这个模式的。两个 agent 在同一个检出目录里工作时,会悄无声息地覆盖彼此尚未完成的修改;或者某个 agent 试图检出一个已经在别处被检出的分支,结果把自己卡死。本文专门讨论 agent 工作流:这个模式隔离了什么、刻意不隔离什么,以及当三个 agent 同时完成任务时该怎么办。

要点速览

  • 每个 worktree 只运行一个 agent:git worktree add ../task-a -b agent/task-a main 为每个 agent 提供隔离的目录和分支。
  • Worktree 隔离的是文件,而非运行时:端口、数据库和 Docker volume 属于整台机器,因此要为每个 worktree 分配独立的端口和数据库。
  • 新建的 worktree 只包含被 git 跟踪的文件;启动 agent 前需要执行 npm ci 并复制 .env
  • 一个分支只能在一个 worktree 中被检出,因此要明确告知 agent 永远不要运行 git checkoutgit switch
  • 逐个合并完成的分支,每个分支在合并前先基于更新后的 main 做 rebase。

面向 AI Agent 的 Git Worktree 模式

「每个 worktree 一个 agent」模式每个任务只需两条命令。基于 main 创建一个带新分支的 worktree,然后在其中启动 agent:

git worktree add ../task-a -b agent/task-a main
git worktree add ../task-b -b agent/task-b main

让一个 agent 在 ../task-a 中工作,另一个在 ../task-b 中工作。每个 agent 拥有自己的工作目录、自己的索引和自己的 HEAD,同时所有 worktree 共享同一套对象数据库和 refs,因此在一个 worktree 中提交的 commit 可以立即被其他 worktree 看到。把 worktree 绑定到任务而不是 agent:工作开始时创建它,分支合并后删除它。Anthropic 在 Claude Code 的 worktrees 指南中记录了同样的手动操作流程,而 Cursor 则通过其 Agents Window 在隔离的 worktree 中运行 agent;Codex 在指向某个目录时的工作方式也是一样的。

为什么共用一个目录会失败?

两个 agent 共用一个目录时会静默失败:当一个 agent 覆盖另一个 agent 未提交的修改时,git 和 agent 都不会报错,因此问题只有到代码审查时才会浮现。Git 的冲突检测是基于 commit 进行比较的;对同一个工作树的并发写入根本不会进入它的视野。

第二类失败更吵闹,但后果更严重。共享目录中的并发 git 操作会在 .git/index.lock 上发生冲突,如果某个 agent 在持有该锁时崩溃,残留的锁文件会阻塞其他所有 agent 的 git 命令,直到有人手动删除它。有些 agent 会重试;另一些则干脆放弃提交,继续在无限膨胀的未提交状态之上生成代码。独立的 worktree 可以同时化解这两个问题,因为每个 worktree 都针对自己的索引进行暂存,而不是共用一个索引。

新建的 Worktree 里没有什么

新建的 worktree 只包含被跟踪的文件,因此 node_modules.env、构建缓存以及其他被 gitignore 的内容在你创建它们之前都不存在。在一个「裸」worktree 中启动的 agent 会在第一次运行测试时失败,或者更糟——「热心地」改写配置来「弥补」这个问题。在 agent 启动之前,先为每个 worktree 做好初始化:

cd ../task-a
npm ci
cp ../main-repo/.env .env

这里用 npm ci 是正确的选择:它专为干净环境设计,严格按照 lockfile 的内容安装依赖。如果你使用的是 Claude Code,.worktreeinclude 文件可以把 .env 这类被 gitignore 的文件带入 Claude Code 自己创建的 worktree,但它对你用 git worktree add 手动创建的 worktree 不生效,因此手动复制这一招对所有工具都适用。

Worktree 不能隔离什么

Worktree 隔离的是文件,而不是运行时:端口、数据库、Docker volume 和共享缓存都属于整台机器,因此从两个 worktree 启动的两个开发服务器仍然会争抢 3000 端口。对于任何通过主机名或端口寻址的资源来说,目录边界毫无意义。

每个 worktree 独立隔离整机共享
工作文件、未提交的修改TCP 端口
索引(暂存区)数据库
检出的分支 / HEADDocker volume 和守护进程
目录内的构建产物全局包缓存

在启动会执行数据库迁移的 agent 之前,先为每个 worktree 分配独立的端口和独立的数据库,因为通过一个 worktree 做出的 schema 变更会落到其他 worktree 共享的任何数据库上。在复制过来的 .env 中同时设置这两项:

# ../task-a/.env
PORT=3001
DATABASE_URL=postgres://localhost:5432/app_agent_task_a

用分支名给数据库命名,可以让你日后轻松识别并清理掉过期的数据库。容器同样可以隔离运行时层,但那涉及的权衡足够单独写一篇文章了。

如何告诉 Agent 它正处于一个 Worktree 中?

一个分支在同一时刻只能在一个 worktree 中被检出,因此试图通过运行 git checkout main 来同步的 agent 会收到报错,并且常常就此卡住。git checkout 手册阐述了这一行为,并指出 --ignore-other-worktrees 是覆盖该限制的方式。不要让 agent 在运行时才发现这一点;把它写进 agent 会读取的指令文件里(AGENTS.mdCLAUDE.md 或等价文件):

You are working inside a git worktree.
Stay on the current branch. Never run `git checkout` or `git switch`.
To sync with main, run `git fetch origin` and `git rebase origin/main`.

当 agent 只需要读取或构建某个特定 commit 时,完全可以跳过分支:git worktree add --detach 会创建一个处于 detached HEAD 状态的 worktree,从而彻底绕开分支独占规则。

git worktree add --detach ../review-b1a2c3 b1a2c3

如何合并这些成果?

Worktree 并不能消除合并冲突;它只是把冲突从静默的运行时覆盖,转移到了合并时可见的冲突,而后者可以用常规的 git 工具处理。当多个 agent 同时完成时,采用串行集成:合并一个分支,把下一个分支 rebase 到更新后的 main 上,再合并,如此往复。

cd ../main-repo
git merge agent/task-a

cd ../task-b
git rebase main
cd ../main-repo
git merge agent/task-b

每次 rebase 都会针对已经合并的全部内容暴露冲突,一次只处理一个分支,而不是把三方冲突堆叠在一起。排序规则其实位于合并的上游:涉及相同文件的任务,或者一个任务消费另一个任务产出的情况,应该串行安排而非并行。任何 worktree 布局都无法拯救一个本来就不独立的任务拆分。

用多少个 Agent,何时清理

实际的上限不是 git,也不是磁盘,而是你的审查带宽——每个并行的 agent 都会产出一个你必须阅读、测试并合并的分支。公开讨论过这套工作流的团队,比如 incident.io 的工程团队,描述了同时运行四到五个 agent 的做法,而大多数开发者会发现更少的数量就已经足够。当一个分支合并后,运行 git worktree remove ../task-a;由于初始化过程添加了未被跟踪的文件,预计需要加上 --force——对于不干净的 worktree,git worktree remove 要求使用该选项。如果目录是被 rm -rf 直接删掉的,则用 git worktree prune 清理 git 仍然保留的过期元数据。

从两个 Agent 开始

「一个任务、一个分支、一个 worktree、一个 agent」把并行 agent 从静默破坏的源头,变成了一组可供审查的分支。先从两个开始:创建 worktree,用 npm ci 和按任务定制的 .env 完成初始化,加上「留在当前分支」的指令,并在扩展到超出你实际审查能力之前,先练熟「先 rebase 再 merge」的流程。

常见问题

并行运行 AI agent 时,我应该使用 git worktree 还是独立克隆?

通常 worktree 更好,因为它们共享同一套对象数据库,在一个 worktree 中提交的 commit 无需 push 或 fetch 就能立即被其他 worktree 看到,而且历史记录在磁盘上只存储一份。独立克隆会复制完整的历史,并且需要 push 和 fetch 来交换 commit。只有在你需要完全隔离时(例如使用了 submodule 的仓库),克隆才更有优势。

git worktree 能用于使用了 submodule 的仓库吗?

只能部分支持。Git 自己的 worktree 手册把 submodule 支持归在 BUGS 一节下,称多重检出为实验性功能,并建议不要在多个位置同时检出一个超级项目。包含 submodule 的 worktree 也无法用 git worktree move 迁移。对于在含有 submodule 的仓库上并行运行 agent 的场景,独立的完整克隆是更安全的隔离机制,尽管它会复制历史,并且需要通过 push 才能在各个检出之间共享 commit。

所有 git worktree 是否共享同一份 git 配置?

是的。除非你主动选择退出,否则每个 worktree 读取的都是同一份仓库配置。要让某个 worktree 拥有自己的设置,请运行 git config extensions.worktreeConfig true,然后用 git config --worktree 写入配置值,这些值会保存在该 worktree 自己的 config.worktree 文件中。代价是:一旦启用该扩展,较旧版本的 Git 将无法打开这个仓库。

删除 worktree 会删掉 agent 的分支吗?

不会。分支是整个仓库共享的 ref,因此 git worktree remove 只会删除工作目录及其元数据,分支及其上的所有 commit 都保持完好。丢失的只有该目录中未提交的修改,这也正是 remove 在未加 --force 时会拒绝处理不干净 worktree 的原因。分支合并之后,再用 git branch -d 单独删除它。

Understand every bug

Uncover frustrations, understand bugs and fix slowdowns like never before with OpenReplay — self-hosted, with full data ownership.

Star on GitHub

We use cookies to improve your experience. By using our site, you accept cookies.