fix(pilot): run 的无人值守合并步骤补 --allow-trunk - #51
Conversation
run.md 全文没有 --allow-trunk,而 git-guard 对 trunk 型 integration 缺这个 flag 直接 die。也就是说【单主干仓库上 pilot run 走到合并那步 必然 exit 3 停住】—— 而 run.md 开头明写「半夜没人回答」。 Brood 自己就是单主干(.pilot.yml: base_branch=main, integration_branch=main), 所以这条在本仓库当前就生效。照旧文档原样跑: $ git-guard.sh merge-pr 45 --integration main --squash git-guard: BLOCKED: integration 'main' is a trunk branch … rc=3 补上之后走到真正该停的地方: $ git-guard.sh merge-pr 45 --integration main --squash --allow-trunk git-guard: BLOCKED: --allow-trunk: PR #45 reviewDecision is 'CHANGES_REQUESTED', not APPROVED — refusing. #48 刚合入的 doctor 第 4 步会把这种配置判为「合法配置,什么都不缺」, 然后 run 走到合并就断 —— 两份文档各自都对,合起来是断的。 不是放宽:--allow-trunk 仍要求分支保护要求审批 + PR 已 APPROVED + 该分支开启 stale-dismissal(#47 加的),三条任一读不到都 fail-closed。 文档里把这三条前提写明,并指向 SKILL.md §doctor 第 4 步。 来源:PR#48 review 标 Medium,当时判「先于本批 PR 存在、不阻塞」记账, 仓库 owner 复核后要求单独修 —— 它让 pilot 的核心能力(无人值守跑到交付) 在 pilot 自己的仓库上跑不完。 Claude-Session: https://claude.ai/code/session_01CmAW1q62bBtjT99inZeyLk
clestons
left a comment
There was a problem hiding this comment.
✅ APPROVE — [2-round,post-R2 全 Low → 按闸门规则跳过 Codex]
+8/-1、纯文档、零运行时改动,修掉的是 pilot 旗舰能力(无人值守跑到交付)在 pilot 自己仓库上的一条硬死路。
三条安全声明逐句核对过源码,全部属实
文案写的是「仍要求该分支的保护规则要求审批、这个 PR 已 APPROVED、且该分支开启了 stale-dismissal,三条任一读不到就 fail-closed 拒绝」。对着 git-guard.sh 逐条:
| 声明 | 实现 | 读不到时 |
|---|---|---|
| 保护规则要求审批 | :322 [ "$approvals" -ge 1 ] || die,approvals 从 required_pull_request_reviews.required_approving_review_count 解析 |
非数字/缺失 → :313/:317 die(还分了「没有保护规则」和「读不到(需 admin)」两种) |
| stale-dismissal | :335 [ "$dismiss" = "1" ] || die,从同一份 $prot 解析(不会和上一条打架) |
解析失败 → 空 → 同样 die |
| PR 已 APPROVED | :341 [ "$decision" = "APPROVED" ] || die |
空 → die |
die() = exit 3。三条都在,三条都 fail-closed。 交叉引用「SKILL.md §doctor 第 4 步」也是真的(#48 已合进 main),措辞一致。
PR 描述里那两条报错我实跑复现过:不加 flag → integration 'main' is a trunk branch(rc=3);加上 → 真的过了 protection + dismiss 两道检查,停在 reviewDecision is 'CHANGES_REQUESTED', not APPROVED。它确实解锁了流程,而且停在了该停的地方。
顺带独立复核了几处此前踩过坑的地方,head 上都是对的:--allow-trunk 由 :197 消费、不会流进 gh pr merge;merge-pr 是 allowlist 不是 denylist,--admin/--repo/--delete-branch 都具名拒绝;protection 读的是 $integration,而 :347 又强制 base == integration,所以「拿到的保护证明」确实属于真正的合并目标。
与 #45 不冲突:#45 已作为 19a5ed8 落 main,本 PR 自己这个 hunk 恰好删掉了那句过时的「受 allow_remote_cleanup 与 dirty-worktree 检查约束」,git merge-tree --write-tree 结果 CLEAN(tree a270454),合并后文案是连贯的。删掉那句也不构成信息丢失:allow_remote_cleanup 在 run.md:87 仍在,dirty 约束在 git-safety.md:28-30。
后续项(全 Low,不阻塞)
1. run.md:70,79 —— 触发条件比脚本窄,同一类卡死换个配置就复现
文档判据是 integration_branch == base_branch,而脚本 :247-251 的 is_trunk 是「名字 ∈ main|master|develop|release*|hotfix* 或 == 仓库默认分支」。
所以 base_branch: main + integration_branch: develop 这种合法的双分支流(develop 还在 run.md:73 的内置保护名单里)不满足文档判据 → agent 不加 flag → 实跑 merge-pr 51 --integration develop --squash 复现 integration 'develop' is a trunk branch,rc=3。这正是本 PR 要消灭的那类无人值守卡死,只是换了一种配置。
改法:判据改成「integration_branch 是主干名(main/master/develop/release*/hotfix*)或等于仓库默认分支」;或者反过来在 git-guard 里把 is_trunk 也纳入 == base_branch,让文档和脚本只有一个真相源。
2. run.md:81 —— 死路是被搬走了,不是被填上
§1 对新触及的这三道 gate 的 rc=3 没有任何恢复路径,而主循环 :59 明写「唯一允许停下的地方是 §3 和 BLOCKED」。而且开了 stale-dismissal 之后,APPROVED 这道 gate 天然有竞态:pr-monitor.sh 读到 APPROVED → 有人 push → 审批被撤 → git-guard.sh:341 die。无人值守正好在本 PR 解锁的那一步撞上未定义行为。
改法:加一行统一处置——「merge-pr rc=3 → 把该 task 标 BLOCKED、原样贴 stderr、回主循环做下一个」,顺手把下面那条 admin-token 的情形也覆盖了。
3. run.md:82 —— 漏了「读保护规则需要仓库 admin 权限」
无人值守用非 admin token 时,gh api repos/…/branches/main/protection 读不到 → :317 rc=3 cannot read branch protection … unverified protection is the same as none。这句读起来像策略违规,实际是 token 权限问题——这是这条流程仅剩的一种死法。建议同句补一句:「读保护规则需仓库 admin 权限;报 cannot read protection 先查 token scope,不是分支没保护」。
4. 同一组前置在仓库里有 4 份、2 个版本
SKILL.md:92(doctor 第 4 步):三条 ✅run.md:82(本 PR):三条 ✅SKILL.md:47(硬约束 2):只两条,漏 stale-dismissalreference/git-safety.md:18-22:只两条,同样漏
建议收敛成一处(doctor 第 4 步),其余三处改成引用,杜绝第五份副本。
5.(Info)run.md:84 —— 「git-guard 会先校验 PR base == integration_branch」与实现顺序相反:base 校验在 :345,排在三道 allow-trunk gate 之后。无安全影响(合并前仍会校验),但单主干仓库上 base 写错时,先看到的是 protection 报错而不是 base 报错。
2-round:R1a/R1b DeepSeek-v4-flash(R1a 1 条自相矛盾被驳回——它先质疑第三条前置不存在、SKELETON 里又说三条都在;R1b S1 的「夸大安全性/漏 fail-closed」两半被原文驳回,只保留 admin-rights 那半并降为 Low;S2「stale-dismissal 不充分」被脚本 :259-278 自己的注释驳回)→ R2 Opus 独立评审(读真源 + 对 Brood/main 实跑只读校验 + git merge-tree 验 #45 合并)→ post-R2 severity gate 全 Low,按 skill 规则跳过 Codex R3 → R4 Opus 终裁 + 全量复扫(补出触发条件比脚本窄这条实跑复现的 Low、第四份分叉副本、以及 :84 的顺序描述)。
问题:pilot 在自己的仓库上跑不完
run.md全文没有--allow-trunk,而git-guard.sh对 trunk 型 integration 缺这个 flag 直接 die。也就是说单主干仓库上
pilot run走到合并那一步必然 exit 3 停住 —— 而run.md:10开头明写「无人值守意味着半夜没人回答」。
Brood 自己就是单主干(
.pilot.yml:base_branch: main/integration_branch: main),所以这条在本仓库当前就生效。照旧文档原样跑:
补上之后,它走到真正该停的地方:
两份文档各自都对,合起来是断的
#48 刚合入的 doctor 第 4 步会把这种配置判为「这是合法配置,什么都不缺」,并提示合并时用
--allow-trunk。然后
run拿着自己那条不带 flag 的命令走过去,断在合并。doctor 知道配置和合并 flag 是连着的,run不知道。改动
run.md两处(§护栏摘要的示例块 + §1 的实际合并命令),各加一句单主干条件。纯文档,零运行时改动。并把
--allow-trunk的三个前提写明:分支保护要求审批、这个 PR 已APPROVED、该分支开启 stale-dismissal(#47 加的第三条),任一读不到都 fail-closed 拒绝,并指向
SKILL.md §doctor 第 4 步。所以这不是放宽 —— 是让文档跟上守卫的现实。
来源
PR#48 review 标 Medium,当时判「先于本批 PR 存在、不阻塞本 PR」记账。仓库 owner 复核在途账本时
要求单独修掉:它让 pilot 的核心能力(无人值守跑到交付)在 pilot 自己的仓库上跑不完,而账本里其余
open 项都没有这个性质。
preflight.sh run4/4 通过。https://claude.ai/code/session_01CmAW1q62bBtjT99inZeyLk