Git 工作树并不是编码代理的隔离边界
所有发布 git 大多数并行运行 AI 编码代理的工具都为每个代理提供一个 git 工作树。工作树与您的真实库共享 refs、配置、stash 和钩子。在主库中显示一个代理可以执行代码并重写您的提交身份的粘贴和运行复现,以及显示一个恰当隔离的克隆成本相同的基准测试。阿历克斯·查普林斯基 2026 年 7 月 30 日 · 12 分钟阅读 为编码代理提供自己的 git 工作树,这是大多数并行运行代理的工具所采用的方式,该代理可以安装一个钩子,在您下次在真实库中提交时在您的机器上运行。它可以重写您自己提交的电子邮件。它可以将另一个代理的 stash 弹出到它自己的树中。这些都不需要错误或特殊命令。工作树从来不是一个边界。它是连接到一个 .git 的第二个工作目录,所有有趣的事物都生活在那个 .git 中。这里工作树之所以成为默认选项,是因为它们是显而易见的答案。一条命令,无需重复的历史,第二次检出大约只需一秒。宣传的隔离几乎没有成本,但这一两者都被证明是错的:隔离远比“隔离工作树”所暗示的要薄得多,而正确执行的成本是相同的。早期版本的这篇文章中我对成本的部分是错误的,直到我进行了测量。在链接的工作树中,.git 并不是一个目录。它是一个文件:$ cat ../my-worktree/.git gitdir: /path/to/repo/.git/worktrees/my-worktree 一切都来自这个路径。Git 将库状态分成每个工作树状态和公共状态,它会告诉您哪一个是哪个:$ cd ../my-worktree $ git rev-parse --git-dir # 每个工作树 /path/to/repo/.git/worktrees/my-worktree $ git rev-parse --git-common-dir # 与父级和每个兄弟共享 /path/to/repo/.git 每个工作树状态是一个短列表:HEAD、索引、ORIG_HEAD 和一些二分查找和变基文件。其它一切都通过 --git-common-dir 回归到原始库:对象存储(.git/objects),这是关键,并且可以安全共享。引用和分支(refs/heads,packed-refs)。每个工作树一个命名空间。配置(.git/config)。存放(refs/stash)。钩子(.git/hooks),这就是共享目录变成任意代码执行的地方。因此,隔离是真实的,但它是狭窄的:您的工作目录、HEAD 和索引。这些是有用的。它们不是一个边界。边界意味着限制在工作树内的进程无法到达外部状态,而第二个列表就是它可以达到的方式的目录。“隔离工作树”在工具描述和建议线程中经常使用,而从未说明两者中的哪个列表是指的。这让代理能够做的最糟糕的事情。每个块创建自己的库,因此您可以将它们粘贴到一个空目录中并以任意顺序运行。下面的每行输出都是真实的,捕获于 git 2.50.1。执行代码在您的主机上 .git/hooks 位于公共目录中。因而从工作树内部安装的钩子是为父库安装的,它作为您在父库中的身份运行,下次您运行触发命令时。git init -q --initial-branch=main hook-demo cd hook-demo git commit -q --allow-empty -m init git branch agent-work git worktree add -q ../hook-agent agent-work # “隔离”工作树在父级的 .git 中写入一个钩子 cd ../hook-agent cat > "$( git rev-parse --git-common-dir )/hooks/pre-commit" << 'EOF' #!/bin/sh echo " *** hook running as $( whoami ) in $( pwd ) *** " EOF chmod +x "$( git rev-parse --git-common-dir )/hooks/pre-commit" # 现在您,在自己的库中,进行一次普通的提交 cd ../hook-demo git commit -q --allow-empty -m " 一次普通的提交 " # -> *** hook running as you in /path/to/hook-demo *** 这也是为什么工作树不能成为容器化代理的隔离层。将链接的工作树交给容器意味着挂载父级的实际 .git,因为那是工作树自己的状态所在。可写的 .git/hooks 在该挂载的主机侧运行于主机上,容器就失去意义。重写您的提交来源 配置是共享的,所以在工作树内的 git config 写入父级的 .git/config:git init -q --initial-branch=main id-demo cd id-demo git commit -q --allow-empty -m init git branch agent-work git worktree add -q ../id-agent agent-work cd ../id-agent git config user.email " agent@example.com " cd ../id-demo git config user.email # -> agent@example.com git config --show-origin user.email # -> file:.git/config agent@example.com git commit -q --allow-empty -m "您自己做的提交" git log -1 --format=' %an <%ae> ' # -> 您的姓名 <agent@example.com> 静下心来思考最后两行。这不是代理的提交。它是您在自己的库中的提交,包含了别的东西选出的作者行。请取用另一个工作树的 stash 每个库都有一个 refs/stash,它的行为像是一个在一堆中共享的单一栈。
本站免费、广告极少。如果觉得有帮助,可以请我们喝杯咖啡 —— 任何金额都对持续运营有实际帮助。
☕请我喝杯咖啡