如果你同时开三个 Codex CLI 会话在同一个仓库里跑任务,很快就会发现一件让人抓狂的事:Agent A 刚git checkout切到 feature/login 分支,Agent B 那边正在跑的测试直接把构建目录覆盖了;Agent B 想看看自己改的文件,发现工作区里全是 Agent C 的未提交代码。我在连续经历了两次这种互相踩踏之后,决定不再手写一堆git worktree add/remove命令去维护并行环境,而是把它做成了一个专门的 CLI 工具——Worktrunk。
这篇文章想聊聊我为什么觉得 Git Worktree 是并行 AI Agent 工作流里最合适的隔离方案,以及我把这套管理逻辑封装成 CLI 时是怎么设计命令、配置和数据结构的。如果你现在正在用 Codex CLI、Claude Code 这类 AI 编程工具同时处理多个任务,或者你们团队已经开始让多个 Agent 并行提交代码,这篇文章应该能帮你少走不少弯路。
1. Git Worktree 凭什么能扛住并行 AI Agent
1.1 传统分支切换模式在 AI Agent 面前为什么不够用
在传统开发流程里,一个开发者同一时间基本只在一个分支上工作,git checkout切分支的代价是可控的:顶多就是 stash、切分支、恢复依赖、重新编译,一套流程下来三五分钟能搞定。但 AI Agent 的工作方式跟人完全不一样,它可以在你的终端里同时发起几十个会话,每个会话都是一个“不知疲倦、又不太懂协作的实习生”,它们会真的执行git checkout、git pull、git rebase、git clean -fd这些命令。
让多个 Agent 共用一个工作目录,等于让多个实习生共用一张办公桌、一本笔记本:A 在上面写字,B 把纸翻走了,C 直接把笔记本丢进碎纸机。我在实际使用中遇到过的典型事故包括:
- Agent A 切分支导致 Agent B 进程内已经打开的文件句柄全部失效,测试直接崩。
- Agent C 构建时删掉了
dist目录,Agent D 的脚本立刻找不到产物。 - 某个 Agent 执行
git reset --hard把另一个 Agent 刚写好的代码吞掉了,而且因为改动没有提交,连git reflog都不一定救得回来。 - 更烦的是,到复盘时你根本说不清“当前这个目录到底处于哪个任务上下文”。
一个人面对一个仓库还能靠纪律约束自己,但 Agent 不会遵守你的纪律。它只会按照 user prompt 里的指令执行,而很多指令本身就是模糊的。
1.2 git worktree 的核心机制
git worktree是 Git 2.5 引入的功能,理念其实很朴素:一个仓库可以有多个工作目录,它们共享同一个.git对象库,但每个 worktree 都有自己独立的 HEAD、索引文件(index)和工作区文件。
核心命令就几条:
# 创建新 worktree,并基于某个分支或提交创建新分支 git worktree add ../path/to/worktree -b feature/login # 查看所有 worktree 的状态 git worktree list # 移除一个 worktree git worktree remove ../path/to/worktree # 清理已经失效的 worktree 记录 git worktree prune主工作区(main working tree)是git clone时自动创建的,后面通过git worktree add创建的都是 linked worktree。linked worktree 之间在文件层面完全隔离,但git push、git pull、git fetch这些远端操作的结果是共享的,因为 refs 和 objects 都在同一个.git里。
我用一个生活化的类比来理解它:传统的工作区是“一个浏览器里开多个标签页”,切标签的时候会话状态互相串;worktree 是“开多个独立的浏览器窗口”,每个窗口有自己的 cookie、缓存和历史记录,但它们共用同一个书签库。对 AI Agent 来说,它需要的就是这种“工作区隔离 + 版本库共享”的组合。
1.3 为什么 worktree 特别适合 AI Agent 场景
Agent 的工作特点决定了它需要的是同时满足“隔离”和“共享”两个看似矛盾的需求:
- 隔离的是:工作区文件、构建产物、未提交的改动、本地环境变量、Agent 自己的任务上下文。
- 共享的是:Git 对象库、远端跟踪分支、commit 历史,这样每个 Agent 完成后 push 的代码都会汇总到同一个仓库。
如果只用git clone做多个完整仓库副本,隔离是够了,但共享就没了:每个副本都要单独配置 remote、单独 fetch,而且 Agent A 在副本一里 push 的分支,Agent B 在副本二里根本看不见。用 worktree 就没有这个问题:所有 worktree 共享同一套 remote 配置和 branch refs,一个 Agent push 完,另一个 Agent 在同一仓库的其他 worktree 里git fetch就能立刻拿到。
这也是我把 Worktrunk 建立在 worktree 之上、而不是git clone之上的根本原因。
2. Worktrunk 要解决的五个具体痛点
光有git worktree原生命令还不够。我在用了两周之后发现,手敲原生命令在十几个 worktree 面前会迅速失控。我整理了一下,Worktrunk 要解决的核心痛点集中在下面这五个方面。
2.1 痛点一:命名和路径管理混乱
手动执行git worktree add ../repo-feature-login -b feature/login这种命令,时间一长就会出现一堆路径风格完全不统一的工作区:有人习惯用../repo-feature-login,有人用../login-fix-1234,还有人直接默认路径。等到 worktree 数量上到十几个,你会发现自己已经记不清哪个目录对应哪个任务。
Worktrunk 的做法是按任务名自动生成可预测的路径,比如.worktrees/feat-login、.worktrees/fix-issue-1234,拒绝让路径风格随创建者心情变化。
2.2 痛点二:上下文切换成本高
手动切换任务的完整流程是:找到 worktree 目录、激活对应的虚拟环境、设置环境变量、切到正确分支、打开编辑器、重启 Agent 会话。这一套操作在单个任务上看起来没什么,但在十几个任务之间来回切换,每次至少要浪费 5-10 分钟,而且非常容易忘记某个步骤。
Worktrunk 把“进入某个任务上下文”压缩成一个命令,目录、依赖、环境变量、Agent 会话一次就位。
2.3 痛点三:构建依赖和产物互相踩踏
如果每个 worktree 都完全隔离node_modules或.venv,安装一次依赖的时间和磁盘占用会成倍增长;如果完全不隔离,多个 Agent 同时构建时dist、.cache目录又会互相冲突。这里需要一个可配置的依赖策略。
2.4 痛点四:清理回收全靠自觉
分支合并之后,对应 worktree 经常残留,既占用磁盘,又让git worktree list的输出变得冗长。删除 worktree 前还要先处理未提交的改动,步骤繁琐,导致很多人干脆不清理。
2.5 痛点五:Agent 状态完全不可见
几十个 worktree 同时存在时,你根本不知道哪个 Agent 还在跑、哪个已经挂掉、哪个有未提交改动、哪个分支已经合并还没清理。这不是 git 本身能回答的问题,需要一个更高层次的 status 视图。
我把这五个痛点整理成了对比表,方便你理解 Worktrunk 和原生命令的差别:
| 场景 | 原生 git worktree | Worktrunk |
|---|---|---|
| 创建并行工作区 | 每次手动指定路径、分支、依赖 | 一条命令完成创建 + 安装依赖 + 生成任务文件 |
| 查看所有 Agent 工作区状态 | 只有路径和分支,无活跃度/改动信息 | 能看到未提交改动数、Agent 进程、合并状态 |
| 清理已合并 worktree | 手动检查分支 → 确认改动 → 手动 remove | prune自动识别已合并分支并回收 |
| 依赖隔离策略 | 无内置支持,全靠自己设计 | 支持独立、符号链接共享、按目录共享三种策略 |
| 与 AI CLI 集成 | 无,需要自己拼接命令 | 创建后可直接拉起 codex / claude 会话 |
3. 核心命令与设计思路:从 create 到 prune
这一章我详细讲讲 Worktrunk 的命令设计,每个命令背后解决什么问题,以及为什么选这个设计而不是另一种。
3.1 worktrunk create:创建隔离环境
创建命令是使用频率最高的入口,设计目标是“一条命令,任务环境全部就位”:
worktrunk create feat-login --base main --auto-install这条命令实际做的事情包括:
- 在
.worktrees/feat-login路径下基于main分支创建新分支feat-login的 worktree。 - 根据配置文件中的
hooks.after_create自动执行依赖安装,比如npm install或者python -m venv .venv && pip install -r requirements.txt。 - 在 worktree 里生成一个
TASK.md文件,记录任务名称、创建时间、目标分支、关联 issue 编号。 - 可选地,在 worktree 目录里直接拉起你指定的 AI CLI 会话,比如
codex或claude,让 Agent 一开始就在一个干净、独立、有任务说明的环境里工作。
为什么不直接用git worktree add加参数?因为create在这里做的是“工作流编排”,而不是单纯的 git 操作。真正让生产环境可用的是后面自动执行的 hook 链。
3.2 worktrunk switch 与 portal:进入任务上下文
switch的设计目标是让上下文切换不需要思考:
worktrunk switch feat-login它会做三件事:切换到.worktrees/feat-login目录;按配置恢复环境变量;把当前活动任务记录到本地状态文件里,方便status命令展示“我上一次在忙什么”。这个命令对我来说最实用的场景是:早上开工后一条命令回到昨天没做完的任务,不用回忆目录在哪、环境变量是什么。
3.3 worktrunk list 与 status:一眼看清所有工作区
worktrunk list是原生git worktree list的增强版:
worktrunk status输出字段我设计为:
| 字段 | 含义 |
|---|---|
| 路径 | worktree 的相对路径 |
| 分支 | 当前检出的分支名 |
| 状态 | 活跃 / 空闲 / 已合并 / 有冲突 |
| 未提交改动 | 通过git status --porcelain统计的有变动文件数 |
| Agent 进程 | 是否有正在运行的 codex / claude 进程及 PID |
| 最近提交 | 最后一次 commit 的时间 |
为什么要统计 Agent 进程?因为 Agent 经常挂在后台不退出,光看目录看不出它是否还活着。这个字段帮我避免了对一个还跑着的任务做清理操作。
3.4 worktrunk remove 与 prune:安全回收
删除是最容易出事的一步。设计核心是“先检查,再删除”:
worktrunk remove feat-login # 普通删除,有未提交改动或运行中进程时拒绝 worktrunk remove feat-login --force # 强制删除,加 --force 前会再次列出将丢失的改动prune则更进一步,自动扫描所有 worktree,找出“分支已经合并进目标分支”的 worktree,并提示清理:
worktrunk prune --merged-only这样可以避免两个常见的坑:第一,删掉一个还有未提交改动的 worktree 导致代码丢失;第二,删除分支时 Git 拒绝,因为 worktree 还挂着。我建议把prune加进每周的例行维护流程里。
3.5 配置设计:一个文件管理所有策略
Worktrunk 支持项目级配置文件.worktrunk.yml,也可以放在用户目录~/.worktrunk/config.yml作为全局默认。示例配置如下:
worktree_root: .worktrees # 所有 worktree 都放在这个子目录下 default_base: main # 默认基于哪个分支创建任务 dependency_policy: symlink # 依赖策略:isolate / symlink / hybrid shared_dirs: # hybrid 模式下需要共享的目录 - node_modules hooks: after_create: - npm install - cp .env.example .env after_remove: - npm run clean env: NODE_ENV: development配置项为什么这样设计?核心思考是:依赖策略要按项目实际情况调整,而不要写死在工具里。前端项目node_modules多半可以共享;Python 项目的.venv涉及二进制路径,共享容易出现版本错乱。模板路径、默认分支这些都应该是配置而不是参数,让它能直接沉淀进团队的项目模板。
4. 接入真实 AI Agent 工作流实测:三路并行
4.1 实战场景:三路 Agent 同时处理不同任务
我用一个真实的开发场景来展示 Worktrunk 怎么和 Codex CLI、Claude Code 配合。
假设我手头有三个任务并行推进:
- 任务 A:修复登录接口的 token 过期逻辑,分支名
fix-login-token - 任务 B:给用户 API 新增头像字段,分支名
feat-user-avatar - 任务 C:重构现有测试框架,分支名
refactor-test-runner
过去我可能在一个工作区里反复切来切去,现在只需要:
worktrunk create fix-login-token --base main --auto-install worktrunk create feat-user-avatar --base main --auto-install worktrunk create refactor-test-runner --base main --auto-install三条命令下来,三个独立的工作区、三个新分支、三套依赖环境全部就位。接下来在三个终端里分别进入对应目录,再各自启动独立的 AI CLI 会话。
4.2 实测中的完整操作序列
启动会话的流程我固定为:
cd .worktrees/fix-login-token codex # 或 claude --dangerously-skip-permissions一个值得强调的细节是:不要让 Agent 自己去创建 worktree。Agent 会乱跑、乱命名、残留临时目录,最后反而增加管理成本。正确的方式是人在宿主机上用 Worktrunk 统一创建好隔离环境,Agent 只在指定 worktree 目录里做增删改查,改完 push,再恢复宿主机控制权。
在实测中,三个 Agent 并行跑了大约四十分钟,完全没有出现互相踩踏的问题。任务 A 在修改登录逻辑时删掉了dist重建产物不影响任务 C 的测试脚本,因为两个 worktree 的dist目录互不相干。任务 B 的未提交改动也不会被任务 A 的git stash波及。
4.3 对比效果:并行产出与冲突频率
我把并行方式跑出的效果做了个粗略对比:
| 指标 | 单工作区轮流切分支 | 三个独立 worktree 并行 |
|---|---|---|
| 完成三个任务总耗时 | 约 3 小时(含反复切换和恢复环境) | 约 1.5 小时 |
| 代码互相覆盖次数 | 2 次 | 0 次 |
| 构建失败次数 | 4 次 | 1 次(仅一次依赖问题) |
| 收工后清理成本 | 低 | 中等,需要 prune |
并行收益非常明显。切换成本节省还是其次,核心价值在于“每个 Agent 的未提交改动都得到了物理级别的隔离”,这比任何 git 纪律都有用。
4.4 与 IDE 配合:VS Code 多窗口
多个 worktree 在 IDE 中的体验也值得说一说。我习惯用 VS Code 的多窗口模式:每个 worktree 打开一个独立窗口,窗口标题天然区分任务。
code .worktrees/fix-login-token code .worktrees/feat-user-avatar需要注意.code-workspace文件里的文件夹路径是相对路径,所以一个工作区文件无法同时指向多个 worktree。建议各 worktree 内不放工作区文件,直接用终端分别打开目录,或者只在主工作区放 workspace 文件并使用../.worktrees/xxx的相对引用。
5. 使用中踩过的坑与规避方案
工具做得再顺手,Git Worktree 本身的限制和实际环境的复杂情况还是会带来一些坑。这里列几个我真的踩过、也花时间解决了的问题。
5.1 坑一:同一分支不能被两个 worktree 同时检出
Git 规定一个分支同时只能在一个 worktree 上 checkout。如果你在 worktree A 里正处于feat/user-avatar分支,又在 worktree B 里尝试git checkout feat/user-avatar,Git 会直接拒绝。
规避方法很简单,一句话:一个任务一个分支。每个 worktree 只对应一个独立分支,不要尝试在两个 worktree 之间共享同一个分支。如果确实需要看同一个分支的代码,用git show或者开一个 detached HEAD 的 worktree,不要手动 checkout 同名分支。
5.2 坑二:主工作区未提交改动卡住创建流程
worktrunk create需要基于主分支创建新分支,如果主工作区当前有未提交的改动,而创建逻辑内部执行了从主分支拉新分支的操作,Git 会拒绝。
我的解决方案是分两层:create前先检测主工作区状态,有改动则提示是否自动 stash;同时鼓励团队“主工作区保持干净”——所有活跃开发都进 worktree,主工作区只承担查看代码、跑版本管理操作的角色。这个习惯一旦养成了,后面所有自动化流程都会顺畅很多。
5.3 坑三:依赖目录隔离与共享的取舍
依赖策略最让人纠结。我试过三种模式:
- 完全隔离:每个 worktree 独立安装,互相彻底不干扰,但 5 个 worktree 就是 5 份
node_modules,磁盘和安装时间都受不了。 - 完全共享:所有 worktree 的
node_modules都符号链接到同一个目录,安装一次就够了,但一旦两个任务的依赖版本不同,hoisting 出来的目录结构就会打架,经常出现“这个 worktree 能跑,那个 worktree 报了奇怪的模块找不到错误”。 - 符号链接共享 + 关键目录独立:默认共享
node_modules、.cache这类体积大且基本稳定的目录,遇到需要独立的部分再切回独立模式。
现在 Worktrunk 的默认策略是第三种。dependency_policy: symlink表示把shared_dirs里的目录通过符号链接共享到每个 worktree,其他目录保持独立。如果某个任务明确要升级依赖、改动 lockfile,就在这个任务上单独切换成isolate模式。
5.4 坑四:删除分支前必须先移除 worktree
这是新手最容易撞上的:分支合并之后想删掉本地分支,结果git branch -D报错,提示 branch is checked out at 某个 worktree 路径。因为对 Git 来说只要 worktree 还挂在那个分支上,分支就是“被使用中”的状态。
正确的删除顺序是:
worktrunk remove feat-login git branch -D feat-login所以在 Worktrunk 的prune里我实现了两步清理:先git worktree remove,再删分支。这个顺序一旦反了,就会卡住,而且报错信息会让你误以为是 Git 出了 bug。
5.5 坑五:Windows 长路径与文件锁
如果你在 Windows 上开发,worktree 的默认路径嵌套层级会非常深:仓库路径 +.worktrees+ 分支名,很容易超过 MAX_PATH(260 字符)限制。实测中,一个位于C:\Users\username\projects\repo-name\.worktrees\feat-user-avatar的目录,已经接近极限。
解决方案有两个:一是配置worktree_root到一个短路径,比如D:\wt\repo-name;二是开启 Windows 系统级长路径支持。另外 Windows 上某些文件正在被进程占用时,worktree remove 会失败,需要先关掉占用的编辑器或 Agent 进程。
5.6 坑六:Agent 进程残留导致 remove 失败
AI Agent 有时候没有正常退出,进程还挂在后台,把 worktree 目录里的文件锁住。worktrunk remove在检测到 worktree 内有运行中的 Agent 进程时会拒绝删除,防止你把一个还在跑的任务的工作区拆掉。
如果确认进程已经无用,可以:
worktrunk remove feat-login --force--force会先重新列出该 worktree 的未提交改动和进程 PID,等二次确认再执行。这比rm -rf安全得多。
6. 进阶玩法:任务状态感知与自动回收
6.1 任务状态文件约定
我在每个 worktree 里放了一个TASK.md,用来记录这个任务的背景、当前进展、阻塞点。这是个人为约定,但实测发现收益很大:Agent 在读TASK.md后能迅速理解上下文,不需要我在 prompt 里反复粘贴任务描述。
# 任务:修复登录 token 过期逻辑 - 状态:进行中(更新到 2025-01-15) - 目标分支:fix-login-token - 改动范围:auth service、login controller - 当前问题:token 刷新接口返回 401Worktrunk 的status命令解析这个文件,把任务的“状态”字段展示在列表里,这样我就有了一张“任务墙”,不用打开每个目录才知道进展。
6.2 分支合并后自动 prune
工作流里最容易被忽略的一步是清理。我的做法是在worktrunk prune里加入--merged-only选项,自动扫描所有 worktree:如果某个 worktree 的分支已经合并进main(或default_base指定的分支),就标记为“可回收”,然后批量移除 worktree 并删除对应分支。
这条命令配合 git hook 后的效果:
worktrunk prune --merged-only --delete-branch开发流程收尾时的操作变成:合并 PR → 回到主工作区 →worktrunk prune→ 一切干净。我建议团队把 prune 挂在post-mergehook 里,或者用 cron 定期执行,避免 worktree 只增不减。
6.3 与 CI preview 环境的打通
每个 worktree 独立运行之后,还能进一步和 CI 结合:每个 worktree push 分支后触发一个独立的 preview 部署。这样每个 Agent 的产出都有自己的预览环境,验证时互不污染。
这个能力不需要 Worktrunk 内置,做一层脚本封装即可:
# 在 CI 配置里 worktrunk list --format json | jq -r '.[] | select(.merged == false) | .branch'拿到所有活跃分支后,对每个分支触发 preview 构建。实测下来,Agent 提交的代码可以快速自动部署并回传验证结果,整个反馈链路比人肉验证快很多。
6.4 后续扩展:与 MCP 集成
还有一个我规划中的方向:把 Worktrunk 的能力通过 MCP(Model Context Protocol)暴露给 Agent,让 Agent 能直接调用worktrunk list、worktrunk status、worktrunk switch等工具。这样一来,Agent 在开始任务前可以自己查看有哪些 worktree、哪些分支可用,而不是由人手动指定。
不过这个方向需要谨慎设计权限边界:Agent 不应该在未经确认的情况下删除 worktree 或切换分支,否则又把并行的隔离优势破坏掉了。现在阶段我的原则是:Worktrunk 负责创建和管理隔离环境,Agent 在隔离环境内部自由发挥,二者边界分明。
6.5 一个小技巧:shell 别名与自动补全
最后分享一个提升日常使用体验的小技巧。Worktrunk 支持 shell 补全,加上几个别名之后,日常输入成本会明显下降:
alias wk='worktrunk' source <(worktrunk completion bash) # zsh 用户换成 compdefwk create fix-login、wk status、wk prune --merged-only这些短命令用顺手之后,你可能就再也回不去手动敲git worktree add的时代了。
我自己用了两个月 Worktrunk 之后,最大的体会不是省了多少磁盘空间,而是终于能在下午复盘时一眼看出:今天三个 Agent 分别做了哪些事、改了什么分支、哪些 worktree 该回收。这种“可观测性”带来的掌控感,才是并行 AI Agent 工作流真正需要的东西。如果你也在折腾大量 Agent 并行开发,建议先小规模试试 Git Worktree,再用 Worktrunk 把接口统一起来,你会感受到明显差异。