关于 pr-babysit
用于持续跟进 GitHub PR 或 GitLab MR 的技能,直至 CI 通过且可执行的审查意见得到处理。它会分类评论、提交针对性修复、在相关讨论串中回复,并检查重复或由修复引入的问题。
主要功能
- 01根据仓库 remote 识别 GitHub 和 GitLab
- 02分类审查意见与失败检查
- 03以 conventional commits 提交针对性修复
- 04发布回复并确认其位于讨论串内
- 05轮询 CI 并运行自反馈循环关卡
适用场景
- 01处理开放 PR 或 MR 中可执行的评论
- 02修改代码前诊断失败的 CI 检查
- 03停止处理反复修复后仍未解决的问题
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ kirkchen/ cadence --skill pr-babysit安装后在 Claude Code 中输入/pr-babysit确认已加载。
详细介绍
pr-babysit
持续跟进 PR/MR,直到 CI 全部通过且所有有效的评审反馈都得到处理。支持 GitHub PR(通过 gh)和 GitLab MR(通过 glab)——根据 git remote get-url origin 自动检测(github.com → gh;gitlab.com / 自托管 GitLab → glab)。
参数
$ARGUMENTS — 可接受:
- 为空 → 当前分支的开放 PR/MR
- 一个数字 → 当前仓库中对应 IID 的 PR/MR
- 一个 URL → 从中解析 owner/repo + IID
如果当前分支匹配到多个 PR/MR,则停止并询问用户要处理哪一个。
回复语言
发布到 PR/MR 讨论串中的回复正文——即每个回复模板锚点后面的 <what changed> / <reason> / <evidence> 内容,以及 Wontfix Template 字段中的正文——使用 PR/MR 描述的主要语言。其他所有内容均使用英语:锚点短语本身、Wontfix Template 字段标签、常规提交前缀、race 元标签、P-code / 严重程度 / 理由标记(与 pr-review 的 Output Language 中的规范集合相同)。
如果 PR 描述缺少实质性正文,则依次回退到关联 issue 正文,再回退到英语。
终端输出(第 6 步运行报告、Gate A / Gate B 审计消息、不可见发现提示)保持英语——这些内容会发送到调度器会话,而非 PR。
循环
1. 快照
获取:PR/MR 元数据 + head SHA、所有检查 / 流水线作业、所有评审评论、所有普通评论、所有评审讨论串 / 讨论(含已解决状态)、当前用户登录名。
对于此 PR 中你之前回复过的每个讨论串,缓存 {file path, rule code or primary keyword, your reply summary}——供第 2 步去重使用。
按内容筛选,而非按作者筛选:
- 丢弃正文仅包含 CI 状态行的评论(构建通过/失败、部署事件、“流水线已成功”)。这些是噪声。
- 保留包含可操作信号(
Suggestion/Warning/Critical/Issue/quality gate/failed/ 行级评审备注)的任何评论——即使来自机器人账号。AI 评审机器人、SonarQube、Snyk 属于_内容_机器人,而非噪声机器人。 - 丢弃你之前的回复和已解决的讨论串。
2. 分类
硬性门槛——不可见发现:如果某项检查失败,但实际发现列表位于 CLI 无法访问的外部仪表板(SonarQube、Snyk、DataDog 测试报告等——无令牌、无可访问的 API 端点),则立即停止并请用户粘贴这些发现。不要在本地复现,也不要把“猜测”的发现当作完整周期来处理。不要先处理不相关的反馈,而让不可见发现悬而未决。根因诊断以能够看到发现为前提;看不到时,必须首先触发此门槛。
跨轮去重——对于每条新评论,检查第 1 步的缓存:
- 文件相同且规则代码(例如
CA1031)相同,或主关键词相同,且对应的讨论串已回复过 → 视为重复。用一行回复链接到较早的讨论串,不要重新实现或解释。 - 同一问题在尝试修复后仍连续 3 轮存在 → 升级为
needs-user-input(机器人陷入停滞;需要用户来打破僵局)。
反馈——将剩余的未解决评论分类:
- 有效 — bug、安全问题、逻辑错误、明确可执行的建议
- 讨论 — 含义模糊、可能误读了源码、设计权衡、范围不明确 → 不要自主回复,不要实现——收集起来交由用户处理
- 超出范围 — 明显不属于此 PR 声明的目标范围 → 收集起来交由用户处理
检查——对于每项失败的检查:通过 CLI 获取失败日志,并在尝试修复前诊断根因(没有明确原因就不要打补丁)。区分真实失败与偶发失败;仅在有证据表明是偶发失败时才重试。如果失败日志不包含实际发现,则触发上述不可见发现门槛。
3. 处理(仅限有效问题和真实故障)
对于每项:
- 实施修复。
- 提交小型提交,遵循 Conventional Commits 格式,每个提交只包含一项逻辑更改。类型速查:行为变更 →
fix;不改变行为的结构调整/可读性改进(包括 lint 抑制)→refactor;非源代码内容(CI、husky、工具)→chore;纯文档 →docs。 - 在原始评论/讨论串中回复(使用下方模板表)。
- 确认回复已发布在讨论串内,而非顶层评论中(参见下方“回复端点”)。
各平台的回复端点——端点不匹配会产生孤立的顶层评论:
| 操作 | GitHub | GitLab |
|---|---|---|
| 回复审查讨论串 | POST /repos/{O}/{R}/pulls/{id}/comments with in_reply_to_id | POST /projects/:id/merge_requests/{iid}/discussions/{disc_id}/notes |
| 新建顶层评论 | POST /repos/{O}/{R}/issues/{id}/comments | POST /projects/:id/merge_requests/{iid}/notes |
发布回复后,获取讨论/审查讨论串并确认你的评论位于讨论串内(评论数 ≥ 2,且包含你的用户名)。如果评论发到了顶层 → 删除它,并通过正确的端点重试。
回复模板——根据情况选择:
| 情况 | 模板 |
|---|---|
| 已采纳并修复 | Addressed in <SHA> — <what changed>. |
| 有意为之的设计,不作更改 | Deliberate design — <reason>. <spec or codebase ref>. |
| 此 PR 中较早的回复已处理相同问题 | Same as the earlier <topic> thread — <link>. |
| Bot 的前提有误,不予修复 | Won't fix — premise doesn't hold. <evidence: file:line / spec section>. |
“有意为之”/“不予修复”模板旨在保持语气中立、以证据为依据——没有模板时,回复往往会变成防御性辩解或实现细节堆砌。
锚点短语保留英文;仅根据 PR 描述所用的语言调整每个锚点后面的正文。参见回复语言。
Lint/警告抑制——任何 #pragma、// eslint-disable、# noqa、@SuppressWarnings 等内容都必须包含:
- (a) 同一行内的理由注释;以及
- (b) 对规范章节的引用,或现有代码库中因相同原因使用相同抑制方式的先例(
file:line)。
若 (a) 或 (b) 任一项不可用 → 不要抑制,而应重构。适用 (b) 时,在提交信息中注明先例 file:line。
硬性规则:
- 不得对已推送的提交使用
--amend - 不得
--force-push - 除非审查者明确要求,否则不要将 GitLab 讨论标记为已解决
- 不得在未回复的情况下关闭任何审查者讨论串
- 对同一项修复连续失败 3 次 → 停止,记录失败原因及需要质疑的假设,并交还用户处理(遵循全局 CLAUDE.md)
4. 推送并等待
执行 git push。轮询 CI,直到进入终态(GitHub:gh pr checks --watch;GitLab:轮询 head_pipeline.status,直到成功/失败/已取消)。
4.1 记录 prior_fix_range
步骤 3 的修复提交合入且步骤 4 已推送后,记录涵盖本轮修复的 SHA 范围。此范围是以下两个下游使用方的权威事实来源:
- 下一轮的 pr-review 调用——将其作为
prior_fix_range输入传入,以便 pr-review 的增量模式能够应用丢弃信号(B),识别自身引入的表面 - 下文步骤 4.5 的 Gate B——使用同一范围和同一行级归因机制
# After step 4 push, before invoking the next pr-review iter:
FIRST_FIX_SHA=$(git log --format='%H' "$PREV_HEAD..HEAD" | tail -1) # oldest fix in this iter
LAST_FIX_SHA=$(git rev-parse HEAD) # newest fix in this iter
PRIOR_FIX_RANGE="${FIRST_FIX_SHA}^..${LAST_FIX_SHA}"
将 PRIOR_FIX_RANGE(以及作为下一轮 $PREV_HEAD 的 $LAST_FIX_SHA)保存到 babysit 状态文件或会话环境中。如果本轮只推送了一个提交,则 FIRST_FIX_SHA == LAST_FIX_SHA,范围将简化为 <sha>^..<sha>。
如果本轮未推送任何提交(仅重新运行 CI)→ 无需记录修复范围;下一轮跳过 Gate B 的自身引入检查,但仍照常运行 Gate A。
为何不在 Gate B 执行时再计算:在推送时计算可确保范围精确对应解决第 (N-1) 轮问题的提交。在 Gate B 执行时才计算,可能会包含用户在两轮之间手动编辑分支时产生的无关提交。
4.5 自反馈循环门控
推送本轮修复并等待 CI 通过后,在返回步骤 1 之前,运行两个子门控,以捕获不同的自反馈故障模式。如果没有这两个门控,自动化审查器与自动化 babysitter 搭配时,可能会连续 N 轮追查测试卫生问题(Gate A),或追查层层叠加的竞态表面(Gate B)。
两个门控都会解析此 PR 上 pr-review 的行内评论:
gh api repos/$OWNER/$REPO/pulls/$N/comments \
--jq '[.[] | select(.body | contains("").j),
race_meta: (.body | capture("\\[window=(?<w>[^,]+), damage=(?<d>[^,]+), recovery=(?<r>[^\\]]+)\\]") // null)}]'
仅保留自上一轮 HEAD SHA 之后创建的问题(即本轮引入的新问题)。
Gate A:收益递减(仅涉及卫生问题的轮次)
触发条件:以下条件全部满足:
- 本轮有 ≥1 个新的 pr-review 问题
- 没有任何新问题的
justification ∈ {Reachable, Precedent, Asymmetric, Historical} - 所有新问题的
justification=Hygiene(或缺失——缺失时视为 Hygiene)
操作:停止自动循环,跳过步骤 5 的常规判断,跳转到步骤 6,并执行:
Status: needs-user-input (diminishing returns)
This iter's pr-review surfaced only hygiene findings — no Reachable / Precedent /
Asymmetric / Historical justification on any new finding.
Hygiene followups (N):
<list — id, slug, file:line, one-line failure mode>
Continuing the loop will likely surface more hygiene from the same code paths.
Your call:
(s) ship — open a single follow-up issue collecting the hygiene items, mark PR ready-to-merge
(p) polish — keep looping (override the gate for this round)
(r) re-review-full — challenge whether the self-loop missed anything (force `mode=full` on next pr-review)
Gate B:收敛审计(竞态叠加轮次)
用于捕获以下故障模式:第 (N-1) 轮的修复引入新的竞态/状态转换表面,审查器将其标记为 Reachable 问题,下一轮的修复又引入另一个竞态表面,如此无穷无尽。Gate A 无法捕获此情况——这些问题带有 justification=Reachable,单独来看都成立;只有在聚类层面才能看出其发散趋势。
prior_fix_range:使用步骤 4.1中记录的范围。该范围与传给 pr-review 增量模式调用的范围相同,因此 Gate B 的自身引入检查和 pr-review 的丢弃信号(B)依据的是完全相同的证据。如果步骤 4.1 未记录任何内容(第 N-1 轮未推送提交),则不触发 Gate B——没有第 (N-1) 轮的修复表面可供收敛审计。
触发条件:以下条件全部满足:
iter ≥ 3(前两轮属于正常审查节奏,不视为发散)- 本轮有 ≥2 个新问题引用
prior_fix_range内的file:line——即批评第 (N-1) 轮新添加的表面 - 其中 ≥2 个问题属于竞态类别——按以下条件的逻辑或进行检测:
- (i) 带有 pr-review 竞态类别元数据要求所规定的
[window=..., damage=..., recovery=...]元数据,或 - (ii) slug/类别关键词匹配以下任一项:
race | TOCTOU | concurren | sweep | lifecycle | state-transition | debounce | claim | lease | fence | stale | orphan | race-window,或 - (iii)
\bwindow=(即使完整元数据格式错误,也能匹配元数据标签前缀),或atomic.*race | race.*atomic(要求两者同时出现,以避免误匹配数据库事务中的atomic和前端视口中的window) 关键词设计说明:有意排除单独出现的window和单独出现的atomic——它们会在速率限制器、视口和数据库事务正确性相关注释中产生误报。TOCTOU是规范的安全竞态术语,与绕过 meta-tag 路径的 Codex 发现相匹配。debounce / claim / lease / fence涵盖分布式锁定词汇;stale / orphan涵盖清理竞态描述。
- (i) 带有 pr-review 竞态类别元数据要求所规定的
如何验证 prior_fix_range 中的 file:line:
git diff --name-only $prior_fix_range # files touched
git diff -U0 $prior_fix_range -- <file> # line-level attribution
操作:停止自动循环,针对该集群运行 Convergence Audit。对于每个竞态类别的发现,应用 Wontfix 模板 的五步决策:
- 时间窗口:估算竞态操作之间的毫秒/秒/分钟/小时数(如果存在 meta tag,则使用它)
- 损害:分类为
data-loss | deadlock | inconsistency | latency | marginal - 非对称检查:失效模式是否涉及安全、数据完整性或计费?
- 缓解成本:建议的修复是否会引入新的竞态面?
- 恢复路径:容错能力/下一个 webhook/清理器是否能覆盖此竞态?
每项发现的审计判定:
| 判定 | 适用情况 |
|---|---|
| modify(非对称) | 理由属于非对称情形(安全/数据丢失/数据完整性/计费)→ 始终修改,无论缓解成本如何 |
| modify(损害门槛) | damage 值为 data-loss / deadlock / inconsistency → 即使理由不正式属于非对称情形,也要修改。这些损害类别不存在可接受的“容错”答复 |
| modify(安全修复) | 非非对称情形,且 damage ∈ {latency, marginal},但缓解措施不会引入新的竞态面 → 修改(不存在竞态中的竞态风险) |
| wontfix-with-template | 非非对称情形 + damage ∈ {latency, marginal} + recovery=has + 缓解措施会引入新的竞态面 → 使用 Wontfix 模板回复。必须满足全部五项条件;缺少任何一项 → 回退为修改 |
| defer-followup | 问题合理,但解决需要基础设施(例如真实数据库测试、模式迁移、新的后台任务),应归入后续问题 |
向用户报告:
Status: convergence-audit (race-of-race detected)
iter (N-1) fix surface attracted N race-class findings this iter (cluster):
<id> <slug> @ <file:line> window=<w> damage=<d> recovery=<r>
...
Audit verdict per finding:
<id>: modify — <reason: Asymmetric / mitigation safe / etc>
<id>: wontfix — <five-field summary from Wontfix Template>
<id>: defer — <followup issue suggestion>
Your call:
(a) accept all verdicts (post wontfix replies via template, address modify items, open defer issues)
(m) modify a specific verdict — say which finding-id and target verdict
(s) ship — accept all wontfix + defer as-is, mark PR ready-to-merge
(p) override audit — treat as normal iter, loop back to step 1
以下情况不会触发 Gate B:
- 集群包含任何非对称发现——非对称情形(安全/数据丢失/数据完整性/计费)会像 pr-review 的丢弃信号(B)一样绕过收敛退出机制。应展示并修改
iter < 3——早期迭代属于正常审查节奏- 缺少竞态类别 meta,且没有 slug/category 关键词匹配——这能使门槛仅针对实际竞态领域;非竞态收敛(例如命名争论)会回退到 Gate A 或正常流程
原理:Gate A 捕获所有内容都属于卫生问题的迭代;Gate B 捕获多个各自有效的竞态发现聚集在新引入的表面上的迭代。二者共同覆盖两种主要的自反馈失效模式,同时不会压制真正的非对称发现或第三方信号(没有 pr-review 元数据的 Codex / SonarQube / Snyk 发现会绕过这两个门,并通过正常的第 2 步去重 + 3 轮升级流程)。
4.6 Wontfix 模板
供步骤 4.5 Gate B(收敛审计)使用,也可作为手动回复模板,用于那些修改后会引入新的竞争面的问题,例如竞态、状态、清扫和原子操作类问题。
五个字段均为最低必填项。缺少任何一项 → 问题就应当修改,而非标记为 wontfix。
Wontfix — deliberate trade-off.
Race window: <ms / s / min / hr> between <op A> and <op B>.
Precondition: <only fires when X is in Y state for N+ time>
Damage if race fires: <not data-loss / not deadlock / only X happens N seconds earlier than ideal>
Recovery path: <new event / cron sweeper / next webhook covers it; user-visible behavior unchanged>
Asymmetric check: <not security / not data-loss / not data-integrity / not billing>
Mitigation cost: <atomic re-check / two-step merge into transaction is doable, but introduces new race-of-race surface at X>
Acknowledged as known trade-off; fault tolerance covers genuinely <abandoned / stranded / dropped> class.
Tracking: <if needed, opened followup issue X>
字段含义:
- 竞态窗口 — 具体的时间估算,不能只写“很短”。紧凑 CAS 用
ms,清扫周期之间的间隔用min,cron 生命周期用hr。审查者需要知道具体量级才能做判断。 - 前置条件 — 竞态要产生影响,系统必须已处于什么状态。如果前置条件很少出现或系统已处于降级状态,则可以接受该竞态。
- 损害 — 具体描述用户或数据会出现什么情况,不能只写“可能会有问题”。如果你无法用一行描述损害,这个问题可能实际上并非可达。
- 恢复路径 — 必须指出具体机制(下一个 webhook / 清扫器运行 / cron / 容错重试)。“大概没事”不算恢复路径。
- 非对称性检查 — 明确声明该问题不涉及安全性、数据完整性或计费。非对称性问题不可标记为 Wontfix —— 应当修改。
- 缓解成本 — 指出拟议修复会引入的新竞态面。“竞态的竞态”是关键论据。
参考示例:PR #148 中的 sweepAbandonedTasklessThreads 两次 UPDATE 竞态 —— Codex 标记“在放弃排队事件之前重新检查线程状态”;两个清扫 UPDATE 之间的竞态窗口只有几毫秒,前置条件是线程已搁置至少 1 小时,损害为 marginal(已搁置的事件只会比理想情况提前几秒进入终态),恢复路径是新的 webhook 会触发重新激活门控。最终发布了 Wontfix,PR 也已合并。
不适用情况:
- 五个字段中有任何一项无法如实填写 → 问题确实存在,应当修改。Wontfix 模板只适用于修改会引入同等或更严重竞态面的特定情况,不是通用的拒绝模板。
- 开发阶段自我审查场景(代码作者和判断理由推理者之间没有独立会话):不要根据主会话的记忆填写这些字段。Babysit 通常在与代码作者分开的会话中运行,这正是 Wontfix 模板能够安全使用的原因 —— babysit 会话此前未对设计作出承诺,因此可以如实推理损害 / 恢复 / 缓解成本。在开发阶段的自我审查循环中(同一会话既编写代码,又推理问题),作者叙事偏差会不断叠加 —— 在对 6 个 LLM 测试的各种框定条件中,无缺陷框定导致的检测率下降最明显(Mitropoulos 等人,衡量并利用 LLM 辅助安全代码审查中的上下文偏差,arXiv:2603.18740)。暂停流程,并选择 (a) 将判断交给独立会话,或 (b) 使用新启动的判断子 Agent,要求它根据代码独立推导
damage/recovery/mitigation cost,而不是依据问题对象中的字段。Deriver 模式的判断子 Agent 尚未开发为技能 —— 在此之前,将开发阶段的 wontfix 判断视为建议,并告知用户。
5. 做出决定
- ✅ 所有检查均通过,且所有有效反馈均已处理 → 报告(步骤 6)
- 🟡 新增评论 / 检查状态在周期中发生变化 → 返回步骤 1
- 🔴 达到 3 次失败停止门槛、不可见问题门槛、去重 3 轮升级门槛,或确实需要人工判断 → 报告,并标记为
blocked/needs-user-input
6. 报告(运行结束时执行,不自动合并)
PR/MR: <link>
Status: ready-to-merge | needs-user-input | blocked
Checks: <green>/<total>
Addressed (this run): <list of SHA → comment ref + one-liner>
Awaiting your decision:
Discuss (I did NOT reply): <list with comment text + my read of the ambiguity>
Out-of-scope: <list> → open follow-up issues for any of these? (y/N per item)
Blockers (if any): <description + what I tried>
Next command: gh pr merge --squash <id> # or: glab mr merge <id>
报告后,如果存在范围外事项,只询问一次:哪些事项需要创建后续问题?只创建用户选定的事项(gh issue create / glab issue create),并编辑每条 MR/PR 评论中的报告回复,添加新问题的链接。
未经询问,我绝不会做的事
- 根据 Discuss 项目回复、关闭或实施更改——列出这些项目,然后停止。
- 未先与用户确认清单,就为 Out-of-scope 项目创建后续问题。
- 合并 PR/MR。即使所有检查都已通过,也只报告可以合并,并让用户自行执行合并。
- 强制推送、修改已推送的提交、跳过钩子(
--no-verify)或绕过签名。 - 无休止地循环——如果一轮没有产生新工作,也没有解决任何问题,就停止并报告。