Diagnosing Bugs
针对难 bug 的 discipline。仅在有明确理由时跳过阶段。
探索代码库时,读 CONTEXT.md(若存在)以建立相关 module 的清晰 mental model,并检查你即将改动区域的 ADR。
Phase 1 — Build a feedback loop
这就是本 skill。 其余都是机械步骤。若你对 bug 有 tight 的 pass/fail 信号 — 在此 bug 上会 red — 你会找到原因;bisection、hypothesis-testing、instrumentation 只是消费它。若没有,盯代码救不了你。
在此 disproportionate 投入精力。要激进。要创意。拒绝放弃。
Ways to construct one — try them in roughly this order
- Failing test — 在能触及 bug 的 seam 上,unit、integration、e2e 皆可。
- Curl / HTTP script — 对运行中的 dev server。
- CLI invocation — 带 fixture input,将 stdout diff 与 known-good snapshot。
- Headless browser script(Playwright / Puppeteer)— 驱动 UI,断言 DOM/console/network。
- Replay a captured trace。 把真实 network request / payload / event log 存盘;在隔离中 replay 过代码路径。
- Throwaway harness。 启动系统最小子集(一个 service、mocked deps),单次 function call 走 bug 代码路径。
- Property / fuzz loop。 若 bug 是「有时输出错」,跑 1000 随机 input 找 failure mode。
- Bisection harness。 若 bug 出现在两个已知 state 之间(commit、dataset、version),自动化「boot at state X, check, repeat」以便
git bisect run。 - Differential loop。 同一 input 过 old-version vs new-version(或两种 config),diff 输出。
- HITL bash script。 最后手段。若必须人工点击,用
scripts/hitl-loop.template.sh驱动 他们,循环仍有结构。捕获的输出反馈给你。
建好 feedback loop,bug 就 90% 修了。
Tighten the loop
把 loop 当产品。一旦有了 某个 loop,tighten 它:
- 能否更快?(Cache setup、跳过无关 init、缩小 test scope。)
- 能否 signal 更 sharp?(断言具体 symptom,非「没 crash」。)
- 能否更 deterministic?(Pin time、seed RNG、隔离 filesystem、freeze network。)
30 秒 flaky loop 几乎不比没有好;2 秒 deterministic loop 是 tight — debugging superpower。
Non-deterministic bugs
目标不是干净 repro,而是 更高 reproduction rate。循环触发 100×、并行、加压、缩小 timing window、注入 sleep。50% flake bug 可 debug;1% 不行 — 持续提高 rate 直到可 debug。
When you genuinely cannot build a loop
停下并明确说明。列出你试过的。向用户要:(a) 能 repro 的环境访问,(b) 捕获的 artifact(HAR file、log dump、core dump、带时间戳的 screen recording),或 (c) 添加临时生产 instrumentation 的许可。没有 loop 不要 进入 hypothesise。
Completion criterion — a tight loop that goes red
Phase 1 完成当 loop tight 且 red-capable:你能说出 一条命令 — script path、test invocation、curl — 且你 至少已运行过一次(粘贴 invocation 与输出),且:
- [ ] Red-capable — 走实际 bug 代码路径,断言 用户 exact symptom,因此可在此 bug 上 red、修好后 green。不是「runs without erroring」— 必须能 catch this specific bug。
- [ ] Deterministic — 每次运行同一 verdict(flaky bug:按上文 pinned、高 reproduction rate)。
- [ ] Fast — 秒级,非分钟。
- [ ] Agent-runnable — 你可 unattended 运行;人工在 loop 中仅通过
scripts/hitl-loop.template.sh。
若你发现自己在该命令存在前读代码建理论,停下 — 直接跳假设正是本 skill 要防止的失败。 无 red-capable 命令,无 Phase 2。
Phase 2 — Reproduce + minimise
运行 loop。看它 red — bug 出现。
确认:
- [ ] loop 产生 用户 描述的 failure mode — 非碰巧附近的另一种 failure。错 bug = 错 fix。
- [ ] failure 多次运行可复现(或非确定性 bug 有足够高 rate 可 debug)。
- [ ] 已捕获 exact symptom(error message、wrong output、slow timing),后续阶段可验证 fix 是否针对它。
Minimise
一旦 red,把 repro 缩到 仍 red 的最小场景。一次 cut 一个 input、caller、config、data、step,每次 cut 后重跑 loop — 只保留对 failure load-bearing 的。
为何 bother:minimal repro 缩小 Phase 3 hypothesis space(更少 moving part 可怀疑),并成为 Phase 5 的干净 regression test。
完成当 每个剩余元素都 load-bearing — 去掉任一都会让 loop green。
在 reproduce 且 minimise 之前不要 proceed。
Phase 3 — Hypothesise
测试前生成 3–5 个 ranked hypothesis。单 hypothesis 会锚在第一个 plausible idea。
每个 hypothesis 必须 可证伪:陈述它做出的 prediction。
Format: "If
is the cause, then will make the bug disappear / will make it worse."
若无法陈述 prediction,hypothesis 是 vibe — 丢弃或 sharpen。
测试前把 ranked list 展示给用户。 他们常有 domain knowledge 可 instant re-rank(「我们刚 deploy 了 #3 的改动」),或知道已排除的 hypothesis。便宜 checkpoint,大 time saver。不要 block — 用户 AFK 则按你的 ranking proceed。
Phase 4 — Instrument
每个 probe 必须映射到 Phase 3 的 specific prediction。一次只改一个变量。
工具偏好:
- Debugger / REPL inspection — 若环境支持。一个 breakpoint 胜过十条 log。
- Targeted logs — 在能区分 hypothesis 的 boundary。
- 永远不要「log everything and grep」。
给每条 debug log 打唯一前缀 tag,如 [DEBUG-a4f2]。结束时一次 grep 清理。无 tag log 会存活;有 tag 的会死。
Perf branch。 性能回归通常 log 是错的。改为:建立 baseline measurement(timing harness、performance.now()、profiler、query plan),然后 bisect。先 measure,再 fix。
Phase 5 — Fix + regression test
在 fix 之前 写 regression test — 但仅当有 correct seam。
correct seam 是 test 在 call site 按 真实 bug pattern exercise 的 seam。若唯一可用 seam 太 shallow(单 caller test 而 bug 需多 caller、unit test 无法 replicate 触发链),那里的 regression test 给 false confidence。
若不存在 correct seam,这本身就是 finding。 记下。代码库架构阻止 bug 被锁住。标给下一阶段。
若存在 correct seam:
- 把 minimised repro 变成该 seam 上的 failing test。
- 看它 fail。
- Apply fix。
- 看它 pass。
- 对原始(未 minimise)场景重跑 Phase 1 feedback loop。
Phase 6 — Cleanup + post-mortem
宣布 done 前必须:
- [ ] 原始 repro 不再 repro(重跑 Phase 1 loop)
- [ ] Regression test pass(或 absence of seam 已文档化)
- [ ] 所有
[DEBUG-...]instrumentation 已移除(grep该 prefix) - [ ] Throwaway prototype 已删除(或移到明确标记的 debug 位置)
- [ ] 最终正确的 hypothesis 写在 commit / PR message 中 — 让下一个 debugger 学到
然后问:什么能 prevent 这个 bug? 若答案涉及架构变更(无好 test seam、tangled caller、hidden coupling),把 specifics hand off 给 /improve-codebase-architecture skill。在 fix 落地 之后 再推荐,不是之前 — 你现在比开始时信息更多。