Skip to content

fix(pilot): 收尾 —— 两条会让人粘贴出坏结果的命令 + 清死配置 - #52

Merged
jhfnetboy merged 2 commits into
mainfrom
fix/cleanup-followups
Aug 6, 2026
Merged

fix(pilot): 收尾 —— 两条会让人粘贴出坏结果的命令 + 清死配置#52
jhfnetboy merged 2 commits into
mainfrom
fix/cleanup-followups

Conversation

@jhfnetboy

Copy link
Copy Markdown
Member

PR#45 第九轮 APPROVE 时附带的建议。挑出会让人照着粘贴、然后得到坏结果的两条修掉,其余记账。

1. §2 对 squash 分支打印 -d,而 squash 后 -d 必然失败

§1b 对同一类证据打的是 -D,§2 一律打 -d。squash 之后原 tip 不是集成分支的祖先,那条 -d 不可能成功。实测照着粘贴:

git worktree remove <path>   → 成功,目录没了
git branch -d sqfeat         → error: the branch 'sqfeat' is not fully merged
                             → 目录已删、分支还在 = 孤儿分支

对照(同一个 fixture):

main 上的版本:  remove with:  git worktree remove <path>  &&  git branch -d sqfeat
本 PR:          remove with:  git worktree remove <path>  &&  git branch -D sqfeat

而在 squash 仓库里这是每一条 —— 也就是这个功能唯一存在理由的那种仓库形态。

2. main_root 取的是「你站在哪个 worktree」,不是主 worktree

--show-toplevel 在 linked worktree 里返回那个 worktree 自己。而按本 skill 自己的教条(一个 task = 一个 worktree),「从 linked worktree 里跑」正是常态,status.md 还让它每次 pilot status 都跑。

后果:§2 把当前 worktree 整个漏掉,反而打印「删掉那个装着真 .git 的主仓库目录」的命令。目前无害(git 两半都拒绝:fatal: '…' is a main working tree / cannot delete branch … used by worktree),但那是一条递给人粘贴的错命令;而在上一个 commit —— §2 还会真执行 git worktree remove 的时候 —— 它是一次对主 checkout 的删除尝试。

改用 git worktree list --porcelain 的第一段(永远是主 worktree),保留 --show-toplevel 作兜底。

实测:从 linked worktree 里跑,现在正确报告自己,不再把主仓库列为候选。

3. 清掉 allow_remote_cleanup 这个死配置

grep 确认没有任何代码读它(#45 移除远程处理之后),而 status.md / SKILL.md / README.md / templates/pilot.example.yml 还在把它当作生效的闸门 —— 也就是在主动指挥模型去用一个已经不存在的能力。四处清掉,改成指向 GitHub 的 auto-delete-on-merge。

run.md 里那处留给 PR#51 —— 它正在改同一个文件,避免冲突。

记账不做

  • FU-13 --remote / --remote-name 已成摆设:仍被解析、仍在 --help 的 Usage 里,但只控制一段从文档流程里已经不可达的说明文字(status.md/run.md 都不再传 --remote)
  • FU-14 --integration X 只决定候选列表,真正执行的 git branch -d 判的是「合进 HEAD」。站在非 integration 分支上,每个真正已合并的候选都会打印 SKIP (not safely merged) —— 方向 fail-safe(只漏删不误删),但 status.md 要求把输出原样转达,于是会如实转达一个假的「未合并」

preflight.sh run 4/4 通过。

https://claude.ai/code/session_01CmAW1q62bBtjT99inZeyLk

PR#45 第九轮 APPROVE 时附带的建议。挑出【会让人照着粘贴、然后得到坏
结果】的两条修掉,其余记账。

## 1. §2 对 squash 分支打印 -d,而 squash 后 -d 必然失败

§1b 对同一类证据打的是 -D,§2 一律打 -d。squash 之后原 tip 不是集成
分支的祖先,所以那条 -d 【不可能成功】。实测照着粘贴:

  git worktree remove <path>   → 成功,目录没了
  git branch -d sqfeat         → error: the branch 'sqfeat' is not fully merged
  → 目录已删、分支还在 = 孤儿分支

而在 squash 仓库里这是【每一条】—— 也就是这个功能唯一存在理由的那种
仓库形态。改成和 §1b 一致:via=git 打 -d,squash 证据打 -D。

## 2. main_root 取的是「你站在哪个 worktree」,不是主 worktree

--show-toplevel 在 linked worktree 里返回那个 worktree 自己。而按本
skill 自己的教条(一个 task = 一个 worktree),「从 linked worktree 里跑」
正是常态,status.md 还让它每次 pilot status 都跑。

后果:§2 把当前 worktree 整个漏掉,反而打印「删掉那个装着真 .git 的
主仓库目录」的命令。目前无害(git 两半都拒绝),但那是一条递给人粘贴的
错命令;而在上一个 commit —— §2 还会真执行 remove 的时候 —— 它是一次
对主 checkout 的删除尝试。

改用 git worktree list --porcelain 的第一段(永远是主 worktree)。

## 3. 清掉 allow_remote_cleanup 这个死配置

grep 确认【没有任何代码读它】,而 status.md/SKILL.md/README/模板 还在
把它当作生效的闸门、指挥模型去用一个已经不存在的能力。四处清掉,改成
指向 GitHub 的 auto-delete-on-merge。

(run.md 里那处留给 PR#51 —— 它正在改同一个文件,避免冲突)

## 记账不做

FU-13:--remote/--remote-name 已成摆设(仍被解析、仍在 --help 里,但只
控制一段从文档流程里已不可达的说明文字)。
FU-14:--integration 只决定候选列表,真正执行的 git branch -d 判的是
「合进 HEAD」—— 站在非 integration 分支上会如实转达一个假的「未合并」。

Claude-Session: https://claude.ai/code/session_01CmAW1q62bBtjT99inZeyLk
@jhfnetboy
jhfnetboy requested a review from clestons as a code owner August 6, 2026 04:47

@clestons clestons left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ APPROVE — [4-round]

两条修复都是真的,我从零重验过;余下四条 Low 建议下一个 commit 收掉,不阻塞。

两条修复实证

L2 main_root —— 从 linked worktree 里跑:

parent:  /private/tmp/l3/repo  [primaryfeat]  (git)
           remove with:  git worktree remove /private/tmp/l3/repo  &&  git branch -d primaryfeat
         ← 递给人一条「删掉主 checkout」的命令,同时把它站着的那个 worktree 整个漏掉
HEAD:    正确跳过主 worktree,评估的是自己

六种拓扑全验过:主 worktree、主里的子目录、linked、linked 里的子目录、bare 仓库的 linked worktree(HEAD 跳过 bare 目录,parent 把它列成候选)、bare 目录本身。符号链接 checkout 下 --show-toplevelworktree list --porcelain 给出的路径形式一致,所以 :419 没引入路径形态变化。

L1 -d-D —— squash 仓库里逐字粘贴:

parent 的 -d:  git worktree remove 成功 → error: the branch 'sqfeat' is not fully merged
               → 目录没了、分支还在 = 孤儿分支
HEAD 的 -D:    Deleted branch sqfeat (was a38d194),exit 0

-D 在这里是对的,虽然它抽象上更狠:squash 之后原 tip 按构造就不是祖先,-d 不可能成功;§1b 对同一类证据本来就打 -D;而且这是打印给人看的,不是脚本执行的。

L3 —— grep 确认没有任何代码读 allow_remote_cleanup,前提成立。git merge-tree --write-tree origin/main fdda4254 rc=0 无冲突;PR#51 已经合了(main tip 1a91dc4)并且已经把 run.md:77 那句删掉了,所以「留给 #51」这个延期已经兑现。via 在所有能走到 printf 的路径上都被赋值(其余路径全 return)。bash -n 干净。


后续项(四条 Low,都不阻塞)

1. :419 —— 本次新引入的一条中断路径(parent 没有)

main_root="$(git worktree list --porcelain 2>/dev/null | sed -n 's/^worktree //p' | head -1)"
[ -n "$main_root" ] || main_root="$(git rev-parse --show-toplevel 2>/dev/null || true)"

这是管道,而文件在 :66 开了 set -euo pipefail:431/:434 那对 set +e/-ehandle_wt 里,位置更靠后)。管道非零 → 赋值非零 → 就地终止在 :419,下一行的 || 兜底根本到不了

对着真脚本复现,两种触发都完全静默

old-git shim(worktree list 退 129)  → HEAD exit=129,parent exit=0 且报告完整
SIGPIPE(head -1 提前关管道)          → HEAD exit=141,parent exit=0
两者 stderr 均 0 字节,输出恰好截断在 "## Worktrees (clean + merged only)" 这一行

这正是同一个文件在 :252-256 亲自记录过的坑list_has 之所以不用管道就是为了躲它),而 :521 那个兄弟 reader 用的是进程替换、免疫。

为什么只给 Low:实测阈值 300 个 worktree 还是 exit 0、500 个才 141(约 20KB sed 输出),--porcelain 从 git 2.7(2016)就有;丢的内容是纯报告,而且非零退出码 + 缺失的 == done == 尾行在调用点都是可见的信号。改法:像 :521 那样用进程替换,或者加 || true

⚠️ 一个需要留意的组合:在 --apply 下,§1 的 git branch -d 删除已经发生了脚本才死;而 status.md 要求把输出原样转达,于是模型会把一次被截断的运行报告成完整的。

2. :508 —— L1 只修了 if 的一条臂,另一条臂带着同一个 bug(但这是旧问题,不是回归)

git branch -d 判的是粘贴时那个 shell 的 HEAD,不是 --integration。从 linked worktree 里逐字复现:git worktree remove 成功 → error: the branch 'gitfeat' is not fully merged目录没了、分支成孤儿,和 L1 要消灭的半成品粘贴一模一样。而本 PR 的 L2 修复恰恰是因为「站在 linked worktree 里」是文档里的常态——这个 PR 的两半在这一点上互相矛盾

不阻塞的理由:parent 对每一行都打 -d,本 PR 修掉的正是 squash 仓库里占 100% 的那一条臂,爆炸半径是被缩小了而不是扩大。另外 FU-14 覆盖不到这条:FU-14 记的是 §1 里 fail-safe 的漏删,而这里 && 把它和一个先执行的、不可逆的文件系统删除绑在了一起。

3. :510 —— 采纳了 §1b 的 -D,没采纳 §1b 的披露

§1b 会打 tip=%s,收尾还有「── listed, NOT deleted. Read the PR numbers above」;§2 这行两样都没有——恢复凭据恰好缺在有 force-delete 的那一处。已演示的陈旧报告窗口:打完报告后往那个干净 worktree 里提交新工作,再粘贴 → worktree remove 成功(新工作已提交所以树是干净的),branch -D 把指向不可达 commit 的指针丢掉;parent 的 -d 在同一场景下会拒绝。这比 R1a 建议的「加一句 force-delete 警告」更对症。

4. .pilot.yml:11 —— 仓库自己的配置仍写着 allow_remote_cleanup: false,而本 PR 改的四份文档都已宣布这个键是死的。合并后核实过,这是仅剩的一处

建议

  • :419 改成 done < <(...) 式的进程替换(和 :521 一致)或加 || true,既恢复 :420 的兜底,也重新遵守 :252-256 自己立的规矩。
  • :507 建议反过来写:case "$via" in squash-*) -D ;; *) -d ;; esac。现在 else 那一臂让 force-delete 成了默认,将来任何一条 via 未设置就走到 printf 的路径会静默拿到 -D
  • :510tip=%s 和 §1b 的收尾句;FU-14 扩一条记 §2 变体(不可逆的文件系统删除先执行,所以「fail-safe 方向」在这里不成立)。

驳回

  • R1a F1 的机理说错了(结论方向对):它说「兜底只覆盖空输出、不覆盖非零退出,且是 old-git 特有」。两处都不对——$( ) 只捕获 stdout,所以空 stdout 本来是能走到兜底的;兜底之所以是死代码,是 set -e 先在 :419 终止了。而且不是 old-git 特有,当前 git 2.50.1 上 SIGPIPE 一样触发。
  • R4 自己的一个假设也被证伪并记录在此:怀疑 BSD sed 遇到非 UTF-8 字节序列会失败(那会让 :419 好触发得多)——APFS 根本不允许创建这种路径的 worktree(fatal: could not create directory … Illegal byte sequence),输入不可能存在。

一句话说明为什么放行

一条实践中够不到的回归(要 ~500 个 worktree 或 2016 年的 git,落在纯报告路径上,还有两个可见信号)+ 一条被这个 PR 缩小了爆炸半径的旧问题,对上两条已验证的真修复——而且修的是这个 skill 在每次 pilot status 都递给人粘贴的命令。这是明确的净改进,够不上阻塞线;四条 Low 属于下一个 commit 或跟进账本。


4-round:R1a/R1b DeepSeek-v4-flash(F1 落点对但机理说错、已驳回其推理;F2 成立但有更对症的替代;R1b 无 finding)→ R2 Opus 独立评审(自建可用的 gh stub 打到 squash 分支、逐字粘贴命令、六种 worktree 拓扑,纠正 F1 机理并补出 :419/:508/:510/.pilot.yml:11)→ R3 Codex 对抗挑战(独立沙箱,0 挑战 / 2 确认:E1 静默退 129/141、E2 孤儿分支)→ R4 Opus 终裁 + 全量复扫(四条全部独立复现,实测出 300/500 worktree 的触发阈值从而把 :419 定为 Low,确认 :508 是 parent 既有行为而非回归,并证伪了自己关于非 UTF-8 路径的假设)。

@jhfnetboy
jhfnetboy merged commit e225677 into main Aug 6, 2026
5 checks passed
@jhfnetboy
jhfnetboy deleted the fix/cleanup-followups branch August 6, 2026 05:20
@github-actions github-actions Bot locked and limited conversation to collaborators Aug 6, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants