ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

gsd-core Worktree Cleanup-Wave 已移除目录容错:基于 git 注册表与 ENOENT 的波次清理语义

gsd-core Worktree Cleanup-Wave 已移除目录容错:基于 git 注册表与 ENOENT 的波次清理语义 【免费下载链接】gsd-coreGit. Ship. Done - Core项目地址https://gitcode.com/gh_mirrors/ge/gsd-core点击查看免费下载本指南围绕 gsd-core 的worktree cleanup-wave清理机制展开讲解其在一波并行 executor 收尾时如何正确对待「工作树目录已被 harness 提前移除」这一常规情形合并照常进行、陈旧的管理条目由 teardown 剪除而不是让整波清理失败。读完你将掌握 cleanup-wave 的身份identity与移除removal判定边界、波前快照的作用以及该行为对应的源码实现与测试证据。引言一次修复改变了整波清理的语义在 gsd-core 的 execute-phase 与 quick 工作流中多个 executor 以 git worktree 形式并行运行每个 executor 完成后编排器orchestrator需要把其分支合并回主线、再清理对应的 worktree 与临时分支。这一收尾动作被收敛为 SDK 命令worktree.cleanup-wave调用点、quick 工作流调用时以--manifest传入一波 executor 的清单并以|| exit 1保证失败闭合fail-closed。本次修复changeset 类型Fixed关联 PR 4612解决了一个此前会让整波清理「卡死」的边界情形当某个条目对应的 worktree 目录已被 harness如 Claude Code 在子代理完成后立即清理其工作树提前移除时旧逻辑因git -C gone-path读取失败而将其误判为branch_mismatch并阻断合并导致该条目无法合回主线且其余条目也连带被搁置。修复后只要 git 的注册信息仍把该路径绑定到清单所声明的分支且该路径经statSync确认确实已消失ENOENT该条目就会被正常合并其陈旧的管理条目由 teardown 阶段的git worktree prune剪除。两个问题、两个证据源身份与移除必须分开回答cleanup-wave 对每个条目都要回答两个截然不同的问题而源码 worktree-safety.cts 明确注释了「两个问题、两个来源每个只回答它真正能证明的那一个」身份IDENTITY——「注册在这个路径上的 checkout 是不是清单所声明的分支」答案来自git worktree list --porcelain。git 永远不会丢掉这条绑定即使目录被rm -rfporcelain 输出仍会打印worktree path与branch refs/heads/branch。移除REMOVAL——「这个目录是真的没了还是仅仅不可读」答案来自statSync的 errno。只有ENOENT才代表「已移除」EACCES、EIO等其他 errno 一律按「不可读」处理照旧阻断。这两个来源互不越权并且两个方向的误判都被实测验证过源码注释中明确标注「measured rather than reasoned about」早期实现用fs.existsSync返回 false 推断移除并在没有 checkout 可读时回退到refs/heads/branch做身份判定这把身份从「注册在此的 checkout 在该分支上」弱化为「存在一个同名的分支」从而可能放行一个无关的兄弟分支。prunable并不是移除测试实测中当父目录权限为 000 时git 会对一个仍然存在的 checkout 输出prunable gitdir file points to non-existent location——它无法遍历父目录因而误报 gitdir 文件缺失。若把prunable当作「已移除」接受就会在一个不可读的 worktree 上合并未提交的工作而这正是 dirty 检查要拒绝的场景。statSync能把二者分开ENOENT 是「没了」EACCES/EIO 是「不可读」。代码层面的实现即confirmedGoneworktree-safety.cts它只认errno ENOENT路径能成功stat即视为存在任何其他 errno 一律视为不可读并保持阻断。波前快照一次 prune 不会搁置其余条目git worktree prune是仓库级的维护操作它会清除所有陈旧的管理条目而不只是当前这一个。实测表明两个已移除的 worktree执行一次prune两个注册条目都会消失。因此如果每个条目都在循环内重新读取git worktree list那么第一个被 harness 移除的条目完成 teardown 后后续条目的注册证据已经没了会被误判为branch_mismatch而全部阻断——这比修复前的 bug 更糟因为「一波并行 executor」恰恰是常态。修复的关键设计是惰性波前快照worktree-safety.cts在第一个真正需要身份判定的条目处一次性读取worktree list --porcelain并缓存为worktreeListSnapshot之后整波条目复用这份快照快照早于任何 teardown 的 prune因此每个条目的注册信息都以「剪除之前」的状态为准惰性lazy保证 happy path 不额外开销一波 worktree 全部在场时根本不会触发这次子进程读取。测试 worktree-safety.test.cjs 精确覆盖了这一点两个条目第一个已被移除触发 prune第二个仍要合并——断言两个条目的状态都是merged_removedpending为空。缺失目录的接受路径合并照常警告照发当git -C worktree rev-parse --abbrev-ref HEAD读取失败时修复不再直接阻断而是先询问 git 与文件系统「为什么」只有「git 仍把该路径绑定到清单声明的分支且目录经statSync确认 ENOENT」才放行absentAndIdentified。任何其他失败形态都是真实的失配该路径注册了别的分支——正是防分支调换swap控制要拦截的git 完全没有列出该路径——清单点名了 git 无记录的东西路径能stat成功或stat失败但 errno 不是 ENOENT——checkout 存在或不可读修复前阻断、修复后依旧阻断。值得注意的是接受路径是在失败点事后判定disambiguate at the point of failure而非提前预判一个成功读取的 present worktree 走原有逻辑分支不符照样branch_mismatch阻断行为完全不变。由于「harness 干净移除了已完成 executor」与「操作者或外部进程移除了该路径」在代码面前是同一个签名git 注册仍在、statSync同为 ENOENT接受常规情形若保持静默会让非常规情形失去唯一的报警信号。因此接受必定伴随一条咨询性advisory警告ACCEPTED_ABSENT_WORKTREE警告代码定义它绝不作为闸门条目照常合并但会把 git 自己的prunable原因原文附上供操作者调查。测试断言该警告携带detail: gitdir file points to non-existent location而非转述worktree-safety.test.cjsgit 某些版本只输出裸prunable标记时detail为null同文件 L2773-L2796。teardown 阶段的额外护栏prune 而非强删且须复查 ENOENT接受为缺失的条目在合并完成后走的是git worktree prune剪除管理条目而不是git worktree remove --force——因为强删会删除可能已重新出现在该路径、且从未通过 rescue/dirty 检查的内容worktree-safety.cts。两个护栏值得注意删前复查从身份判定到合并落地之间是一个宽窗口base 门、deletion 门、scope 门、合并本身都可能耗时worktree 可能在此期间重新出现。因此 teardown 前会再做一次confirmedGone若路径已重现则报worktree_remove_failed并拒绝 prune 与删分支——修复前的 bug 只是阻断这里要防的是不可恢复的状态破坏。prune 的副作用被快照吸收注释明确纠正过「prune 只影响本条目」的错误说法——正因为 prune 会清掉同一波后续条目所需的注册证据身份读取才必须前置为波前快照。对在场的 worktreeteardown 仍是git worktree remove --force锁定的先worktree unlock再重试若 remove 因路径已消失而失败则按absentAndIdentified判据决定是否改走 pruneworktree-safety.cts。完整判定流水线一个条目的八道关卡综合源码 executeWorktreeWaveCleanupPlan一个清单条目在整波循环中依次经过身份判定git -C path rev-parse --abbrev-ref HEAD输出须等于清单branch失败时按上述absentAndIdentified判据决定「接受为缺失」或branch_mismatch阻断隔离。base 校验git merge-base HEAD branch须命中expected_base或allowed_bases#1265否则base_mismatch阻断。删除审计git diff --diff-filterD --name-only HEAD...branch检出删除仅清单declared_deletions声明过的路径被豁免其余以branch_contains_deletions阻断#3003。scope 咨询分支实际变更若超出files_modified声明的范围发出scope_out_of_declared咨询不阻断#2596。SUMMARY 救援把 worktree 下.planning/*-SUMMARY.md未提交产物先复制回主线git cat-file -e HEAD:path判定是否已提交救援失败则summary_rescue_failed阻断worktree-safety.cts。dirty 检查git status --porcelain --untracked-filesall已救援的 SUMMARY 路径从输出中过滤剩余脏行以worktree_dirty阻断此处同样在失败点用absentAndIdentified兼容「检查期间目录又被 harness 移除」的窗口。合并git merge branch --no-ff --no-edit超时判merge_timed_outhook 运行时长的独立预算见 DEFAULT_MERGE_TIMEOUT_MS失败先merge --abort再以rev-parse --verify MERGE_HEAD判定仓库是否仍处于合并中repoRootStillMidMerge是则把后续条目移入pending并中止整波这是唯一的仓库级失败豁免#2852被 kill 的合并还要通过git reset --mergestash pop --index恢复合并残留restoreMergeResidue。teardown在场走 remove/unlock/remove缺失走 prune最后git branch -D删分支删分支失败降级为warning状态而非阻断。每道失败关卡都遵循「隔离isolate」原则单条目问题只影响该条目其余条目继续独立评估只有仓库级合并状态才break整波。工作流中的实际调用方式execute-phase编排器在每个 executor 返回后用gsd_run query worktree.record-agent把{agent_id, worktree_path, branch, expected_base, files_modified, declared_deletions}逐条写入WAVE_WORKTREE_MANIFESTexecute-phase.md波次收尾统一调用gsd_run query worktree.cleanup-wave --manifest $WAVE_WORKTREE_MANIFEST || exit 1quick同样以QUICK_WORKTREE_MANIFEST走worktree.cleanup-wave且测试强制要求|| exit 1的失败闭合语义SDK 的安全拒绝必须浮出水面不得被软回退吞掉worktree-cleanup.test.cjs。清单的初始化形态为{orchestrator_root: ..., worktrees: []}也接受裸数组由worktree record-agent做写时校验字段缺失、分支不符合^(worktree-)?agent-|worktree-wf_命名空间、重复(worktree_path, branch)都会大声失败并给出恢复提示确保写入即能被 cleanup-wave 读回planWorktreeRecordAgent。验证矩阵测试如何锁定新语义worktree-safety.test.cjs 为本次修复建立了完整的判定矩阵场景判定测试位置两个已移除条目第一个触发 prune两个都merged_removed快照生效L2720-L2742缺失但注册匹配 ENOENT合并 ACCEPTED_ABSENT_WORKTREE咨询带 git 原文L2744-L2771裸prunable标记无原因文本接受detail: nullL2773-L2796worktree list读不到exit 128branch_mismatch阻断无注册证据即无身份L2798-L2809真实缺失git 标记 prunablemerged_removed不误伤L2811-L2824结合 worktree-cleanup.test.cjs 对工作流层的约束manifest 作用域、|| exit 1、record-agent 写时校验本次修复形成了「SDK 判定 工作流契约 回归测试」三层闭环常规的 harness 提前清理不再卡住整波而任何真实失配分支调换、不可读目录、未声明删除、脏工作树依旧按原语义阻断——这正是本 changeset 全部四句语义承诺的落点。小结worktree cleanup-wave的本次修复把「目录没了」从「一律阻断」细化为「有条件接受」身份继续由 git 自己的工作树注册信息提供git worktree list --porcelain移除只认statSync的 ENOENT波前快照让一次仓库级prune不再搁置后续条目接受路径伴随ACCEPTED_ABSENT_WORKTREE咨询警告并保留 git 原始原因teardown 对缺失条目只 prune 不强删且删前复查 ENOENT。整个判定链在 src/worktree-safety.cts 中可逐行追溯并由 tests/worktree-safety.test.cjs 与 tests/worktree-cleanup.test.cjs 双向锁定。赞分享【免费下载链接】gsd-coreGit. Ship. Done - Core项目地址https://gitcode.com/gh_mirrors/ge/gsd-core点击查看免费下载相关推荐gsd-core 跨 Wave 依赖偏差下的 Worktree 收尾cleanup-tail 片段解析与安全清理指南gsd core 跨 Wave 依赖偏差下的 Worktree 收尾cleanup tail 片段解析与安全清理指南 导读 本文聚焦 gsd core 的并gsd-core Worktree 清理安全加固基于 per-wave Manifest 的失败关闭fail-closed机制解析gsd core Worktree 清理安全加固基于 per wave Manifest 的失败关闭fail closed机制解析 导读 本文聚焦 GSDgsd-core Worktree 清理 CWD 漂移修复manifest 持久化 orchestrator 根目录的源码级解析gsd core Worktree 清理 CWD 漂移修复manifest 持久化 orchestrator 根目录的源码级解析 Wave 清理逻辑不再因主工上一篇SUSFS4KSU-Module配置指南自定义你的root隐藏策略下一篇告别N1查询Goldiloader让Rails自动按需预加载创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进