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

  1. Failing test — 在能触及 bug 的 seam 上,unit、integration、e2e 皆可。
  2. Curl / HTTP script — 对运行中的 dev server。
  3. CLI invocation — 带 fixture input,将 stdout diff 与 known-good snapshot。
  4. Headless browser script(Playwright / Puppeteer)— 驱动 UI,断言 DOM/console/network。
  5. Replay a captured trace。 把真实 network request / payload / event log 存盘;在隔离中 replay 过代码路径。
  6. Throwaway harness。 启动系统最小子集(一个 service、mocked deps),单次 function call 走 bug 代码路径。
  7. Property / fuzz loop。 若 bug 是「有时输出错」,跑 1000 随机 input 找 failure mode。
  8. Bisection harness。 若 bug 出现在两个已知 state 之间(commit、dataset、version),自动化「boot at state X, check, repeat」以便 git bisect run
  9. Differential loop。 同一 input 过 old-version vs new-version(或两种 config),diff 输出。
  10. 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 tightred-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。一次只改一个变量。

工具偏好:

  1. Debugger / REPL inspection — 若环境支持。一个 breakpoint 胜过十条 log。
  2. Targeted logs — 在能区分 hypothesis 的 boundary。
  3. 永远不要「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:

  1. 把 minimised repro 变成该 seam 上的 failing test。
  2. 看它 fail。
  3. Apply fix。
  4. 看它 pass。
  5. 对原始(未 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 落地 之后 再推荐,不是之前 — 你现在比开始时信息更多。