diagnosing-bugs 精读:没有红得起来的反馈回路,不许进入假设
diagnosing-bugs 把调试切成六个相位,每个相位是一道门而不是一条待办。最重要的那道门在 Phase 1:你必须先有一条已经跑过一次、能对『这个 bug』亮红的命令,才被允许提出任何理论——正文把『先读代码猜一个理论』点名成这个技能专门要防的失败。本文讲清六个相位各自的完成标准、为什么假设必须一次生成 3–5 个且可证伪、带 DEBUG 前缀的调试日志为什么值得,以及它把『找不到正确的接缝』当成一条正式发现。
(本文读的是 mattpocock/skills 快照 5beb35b,2026-10-19。引用原文都能按文中路径回仓库核对。这是「逐个技能精读」系列的第十七篇,聚焦 diagnosing-bugs 一个技能。)
它怎么工作:六个相位,每道门都有可勾选的门槛
skills/engineering/diagnosing-bugs/SKILL.md 有 138 行,是本系列里最厚的几篇之一。它的结构不是「步骤」,而是相位的门:
A discipline for hard bugs. Skip phases only when explicitly justified.
触发方式:/diagnosing-bugs,或者在你说「diagnose」「debug this」、报告某个东西坏了 / 抛错 / 失败 / 变慢时自动上场。它自己也划线——只接「你能说出症状的那一个失败」,不做「这个代码库哪里慢」的巡检。
相位一览:
- Redact(写在最前面,先于一切):展示命令、输出、抓来的产物之前,先把每处密钥换成
<REDACTED>;回路用环境变量构造,凭据留在环境里而不是留在屏幕上;抓来的 artifact 只摘带信号的那几行。脱敏后不够诊断,就明说并来问你。 - Phase 1 建反馈回路——这是技能的全部。
- Phase 2 复现 + 最小化:先确认复现的是用户描述的那个失败(「错 bug = 错修复」),再一个一个地砍输入、砍调用方、砍配置、砍步骤。
- Phase 3 假设:一口气生成 3–5 个排好序的假设,每个都可证伪,并写出预测;列表先给用户看,但不阻塞——人不在就按自己的排序往下走。
- Phase 4 打点:一条假设对一个探针,一次只改一个变量;工具优先级是调试器 / REPL > 定向日志 > 绝不「全都打日志再 grep」;每条调试日志带唯一前缀(示例
[DEBUG-a4f2]),收尾时一条 grep 就能清干净。性能回归走另一条支线:先建立基线测量(计时装置、profiler、查询计划),再二分,先量后修。 - Phase 5 修复 + 回归测试:回归测试写在修复之前,但前提是存在正确的接缝。
- Phase 6 清理:原始复现不再复现、回归测试通过(或把「没有接缝」记下来)、
[DEBUG-...]全清、一次性原型删掉、把哪个假设是对的写进 commit / PR 信息。
Phase 1 的完成标准是全文写得最细的地方,四项可以直接打勾:
- Red-capable:它驱动真实 bug 代码路径、断言用户描述的那个症状,所以能对这个 bug 亮红、修好后变绿——不是「跑起来不报错」,而是「逮得住这个 bug」。
- Deterministic:每次运行同一个结论(不稳定 bug 见下面那条)。
- Fast:秒级,不是分钟级。
- Agent-runnable:你能无人值守地跑;人只在 HITL 脚本里出现。
它给了 10 条构造回路的路线,从最优先的「在能碰到 bug 的接缝上写一个失败测试」,一路排到最后的「HITL bash 脚本」。回路本身还被当成产品来打磨:能不能更快(缓存 setup、砍掉无关初始化、缩小测试范围)、信号能不能更锋利(断言具体症状而不是「没崩」)、能不能更确定(钉死时间、种子,隔离文件系统,冻结网络)。一句判据:A 30-second flaky loop is barely better than no loop; a 2-second deterministic one is tight, a debugging superpower.
它和「一般技能」的差别:先要证据,再要理论
这个技能存在的理由只有一个,而且它自己说得很不客气:
If you catch yourself reading code to build a theory before this command exists, stop: jumping straight to a hypothesis is the exact failure this skill prevents. No red-capable command, no Phase 2.
常规调试(包括 agent 默认的调试)是「读代码 → 猜 → 改 → 再猜」。diagnosing-bugs 把顺序倒过来:先造反馈回路,让回路红给你看,然后才允许想。一旦回路存在,后面所有事(二分、验假设、打点)都是机械劳动——这就是它把全部篇幅压在 Phase 1 的原因。文档把这一点讲得更直白:给定一份 bug 报告,coding agent 默认就是读代码、猜;这个技能负责挡住它。
第二个结构性选择是门的粒度。它不是写「建议先复现」,而是写「没有 X 就不能进下一个相位」,甚至干脆写成 No red-capable command, no Phase 2.
第三是只有一个人工检查点,而且在 Phase 3(假设列表给你看)而不是修复之前——文档里 issue #124 要求的正是「修代码之前先让你确认根因」这道门,还没实现。
第四是承认自己不适用的场景。它诊断一个你能说得出症状的失败,不做性能全景审计(那类技能 issue #431 提过并被关掉);来自别人的原始 bug 报告先走 triage;锁定不了就转 improve-codebase-architecture。
它有意识摒弃了什么
一、摒弃了无回路的假设。 造不出回路时,正确动作是停下来、明确说清、列出试过什么,然后向你要素材:能复现的环境、脱敏后的 artifact(HAR、日志转储、core dump),或者允许加临时生产打点。原话是 Do **not** proceed to hypothesise without a loop.
二、摒弃了「全部打日志再 grep」。 定向日志优先,而且必须带前缀。前缀的作用是把清理从一次人工回忆变成一条 grep:Untagged logs survive; tagged logs die.
三、摒弃了单个假设。 单假设会锚定在第一个像样的念头上,所以强制一次生成 3–5 个、强制可证伪、强制写出预测句式:If <X> is the cause, then <changing Y> will make the bug disappear / <changing Z> will make it worse. 写不出预测的,技能称它为一句「感觉」(a vibe),丢掉的或磨锋利。
四、摒弃了「为了有回归测试而写测试」。 接缝太浅时(单调用方的测试、复现不了触发链的单元测试),测试给的是假信心;此时它明确要求把「没有正确的接缝」当作发现本身记下来,并转交 improve-codebase-architecture。
五、摒弃了会泄密的取证方式。 这是本系列里唯一把「展示什么」写进正文的工程技能:命令、输出、抓来的产物一律先去密钥,回路用环境变量承载凭据,artifact 只引用带信号的那几行。
六、把 HITL 压到最后一位,并让脚本承担追问。 scripts/hitl-loop.template.sh 给两个助手:step(给人看一条指令,等人按回车)和 capture(问一个问题,把答案读回终端让 agent 读到)。脚本注释里有一句很关键的分工:capture observations, and leave signing in to the user as a step. 请你观察,但不请你登录。
原文证据
Phase 1 的自我定位,是整个文件里最重的一段:
This is the skill. Everything else is mechanical. If you have a tight pass/fail signal for the bug (one that goes red on this bug), you will find the cause; bisection, hypothesis-testing, and instrumentation all just consume it. If you don’t have one, no amount of staring at code will save you.
Spend disproportionate effort here. Be aggressive. Be creative. Refuse to give up.
最小化的判据只有一句,但它把「最小」定义得可以当场检验:
Done when every remaining element is load-bearing: removing any one of them makes the loop go green.
Phase 5 的那条例外,是这个技能最像工程判断的一处:
A correct seam is one where the test exercises the real bug pattern as it occurs at the call site. If the only available seam is too shallow …, a regression test there gives false confidence.
If no correct seam exists, that itself is the finding. Note it. The codebase architecture is preventing the bug from being locked down.
顺带记一个快照里的小矛盾:docs/engineering/diagnosing-bugs.md 的 FAQ 还写着「没有任何指令要求 agent 对输出做脱敏」(issue #674,未实现),而同一快照的技能正文最前面就是一整节 ## Redact。文档落后于技能——跟着读的时候以正文为准,这本身也是「技能与它的说明会各自漂移」的一个实例。
可迁移结论
- 把「完成」写成可勾选的清单,而不是形容词。 四项判据(能红 / 确定 / 快 / 无人值守)让「回路够不够好」从争论变成逐项打勾。
- 在最容易走错的分岔口放一道门。 「没有红得起来的命令,不许提假设」这句话的价值,在于它把最诱人的捷径提前堵死。
- 给调试痕迹留一个可 grep 的墓碑。
[DEBUG-a4f2]这种前缀几乎零成本,却让清理变成一条命令、让遗漏变得可检查。 - 假设要成批、要排序、要能被推翻,还要先给人看。 一批 3–5 个 + 预测句式 + 一个不阻塞的检查点,成本极低,收益是直接拿到别人脑子里的领域信息。
- 技能可以主动承认边界。 「没有正确的接缝就是一条发现」比「想办法测一下」诚实,也让下一个人知道该去改架构而不是改测试。
diagnosing-bugs 的 138 行里,真正不可替代的只有一句话:先把能红的回路造出来。 其余五个相位、十个回路构造法、每一条打点和清理规则,都是在替你守住这一句话不被绕过——它防的不是「不会调试」,而是「太想解释」。
讨论
用 GitHub 账号留言;评论保存在公开仓库chengshu-blog-discussions的 Discussions 里。也可通过 RSS 订阅后续文章。