并行 Coding Agent 时 git worktree 怎么清理才安全

并行跑多个 Coding Agent 时,常见做法是给每个任务开一个 git worktree,避免抢同一工作区。任务合完之后,如果不清 worktree,磁盘和分支会堆成垃圾场;清错了——尤其在脏树上 --force,或在即将删除的 worktree 里删自己——又会丢掉未提交改动。下面按 Git 官方 git-worktree 文档,整理一套可复制的卫生流程:何时可 reap、prune 和 remove 差在哪、审批门放哪里。
前置条件:本机 Git 支持 worktree(Git 2.5+ 起有;请用较新版本)。已有一个主克隆,并会用 git worktree add 开并行目录。下面命令在主 worktree 或任意仍保留的 linked worktree 里执行均可,但删除某个 worktree 时,不要站在那个即将被删的目录里操作。
Worktree 在官方文档里是什么
一个仓库可以挂多棵工作树:主 worktree 由 clone/init 得到,其余用 git worktree add 挂上,称为 linked worktree。它们共享同一份对象库,但各有自己的 HEAD、索引与未提交改动。用完 linked worktree 后,文档要求用 git worktree remove 拆除;若有人只删了目录、没走命令,仓库里 $GIT_DIR/worktrees 下的管理文件会变成陈旧项,之后靠 git worktree prune(或 gc 相关过期配置)清掉。
# 新建:常见是新分支名取路径最后一段
git worktree add ../hotfix-login
# 已有分支接到新目录
git worktree add ../review-pr-123 feature/login
# 一次性试验、不建分支
git worktree add -d ../scratch-try
分步一:先列表,再决定谁能删
清理脚本的第一步不是 remove,是把清单打成机器可读格式:
git worktree list --porcelain
# 建议再加 -z,路径含换行时更安全
git worktree list --porcelain -z
Porcelain 输出里,每棵树以 worktree <path> 开头,空行结束一条记录;可见 HEAD、branch、detached、locked、prunable 等字段。人眼查看可用默认表格;脚本只解析 porcelain,避免被列宽与注释打乱。
可 reap 的经验条件(需同时满足,再进入审批):
- 该 worktree 对应的主题分支(或其上的提交)已经合入保护分支(如
main)。 - 工作区干净:无已跟踪文件修改、无未跟踪文件(与
git worktree remove默认要求一致)。 - 未被
lock(便携盘、网络盘上的树常会 lock,强删前要先确认)。
「已合入」可用祖先检查(在主仓库执行,把 WT_HEAD 换成该 worktree 的 HEAD 提交):
# 若 WT_HEAD 已是 main 的祖先,说明其提交已在 main 历史上
git merge-base --is-ancestor "$WT_HEAD" main && echo "merged-into-main"
merge-base --is-ancestor A B 在 A 是 B 的祖先时退出码为 0。注意:若合入用了 squash,主题分支尖端可能不是 main 的祖先,需要改用 PR 状态、cherry 等价检测或「分支已删除且 PR merged」等你们仓库约定的信号,不能只靠这一条。
分步二:remove 掉干净树;脏树先停手
官方语义很干脆:
git worktree remove <path>:只允许干净 worktree(无未跟踪文件、已跟踪文件无修改)。主 worktree 不能 remove。- 不干净的树,或带 submodule 的树,必须加
--force才能 remove。 - 对已 lock 的树,remove 要写两次
--force。
# 推荐:只删确认干净且已合入的
git worktree remove /path/to/linked-wt
# 危险:丢弃该树上所有未提交改动与未跟踪文件
# git worktree remove --force /path/to/linked-wt
对 Coding Agent 自动收尾时,默认策略应是:脏树一律跳过并告警,不要替人 --force。Agent 常在树上留下实验文件、失败半截的补丁;强制删除等于静默扔掉证据。
可 reap ≠ 可 force。合入只说明历史已进主线;工作区脏,仍然可能有你还没提交的修复。
分步三:prune 清的是「管理信息」,不是替代 remove
有人习惯 rm -rf ../old-wt,目录没了,但 $GIT_DIR/worktrees/<id> 还在。这时:
git worktree prune -n -v # dry-run:只报告
git worktree prune -v # 真正删掉陈旧管理项
git worktree prune --expire 1day # 只处理超过时效的缺失树
prune 的对象是「工作树目录已经不存在」的管理条目;它不会帮你安全地拆掉一棵还在磁盘上的干净 linked worktree——那是 remove 的工作。文档也写了:下次请用 remove,别再只删目录。
若 worktree 被挪走过,优先 git worktree repair 重连,而不是 prune 掉再重建。
分步四:从主 checkout 清理,别在「自杀目录」里删自己
操作顺序建议固定:
cd到主 worktree(或另一棵不会被删的树)。git worktree list --porcelain解析候选。- 对每个候选:检查合入信号 →
git -C <path> status --porcelain确认干净 → 人工或规则审批 →git worktree remove <path>。 - 最后
git worktree prune -n看是否还有陈旧项。
# 示例:在主库检查某 linked 树是否干净
git -C /path/to/linked-wt status --porcelain
# 无输出 ⇒ 对 remove 而言通常可视作干净(仍须避开 submodule 等特殊情况)
在即将删除的 worktree 内执行 remove,轻则命令失败,重则当前 shell 的 cwd 已失效,后续脚本继续写文件会写到「已消失的路径」上。Agent 自动化尤其容易踩这个坑:任务结束时 cwd 往往就在该 worktree 里。
分步五:加审批门,再交给脚本
并行 Agent 一多,收尾脚本会变成高权限工具。最低审批门可以是:
- 列表门:脚本只打印「将删除」清单与理由(已合入证据、status 为空),退出码 0,不执行 remove。
- 确认门:需要环境变量如
ALLOW_WORKTREE_REAP=1或交互输入路径 basename 才真正删除。 - 锁门:发现
locked直接跳过;便携盘场景应先worktree unlock并由人确认介质在线。
与 AGENTS.md / 仓库规范配合时,把上述条件写成明文规则,让 Agent 在「任务完成」钩子里只提议 reap,不自行 --force。也可对照 Coding Agent 团队实践 里的收尾习惯。
常见坑
- 脏树加
--force。官方允许,但会丢掉未提交改动;Agent 自动化默认应拒绝。 - 在将被删除的 worktree 内删自己。先
cd回主树,再remove。 - 把
prune当成日常卸载。prune 只扫「目录已消失」的管理垃圾;正常下线用remove。 - 只用分支名判断「已合入」。squash merge 后祖先检查会失败或误判;要与托管平台的 merged 状态对齐。
- 忽略
locked。锁是为了防止便携路径被 gc/prune 掉;强拆前读 lock reason。
完成标准
- 能解释
remove与prune各清什么,并在仓库里成功 dry-run 一次prune -n -v。 - 收尾脚本(或手工清单)包含:porcelain 列表 → 合入检查 → status 干净 → 非 lock → 审批 → remove。
- 至少一次演练:故意留下脏 worktree,确认自动化会跳过而不是
--force。 - 文档写明「清理命令必须在主 worktree 执行」。
最后
Worktree 让 Agent 并行变得便宜;卫生流程让并行不至于变成仓库负债。先把 list --porcelain 和「脏树不 force」写成团队默认,再谈自动 reap。需要对照命令细节时,直接打开 Git 官方 git-worktree 页,以你本机 git version 对应的行为为准。