← 技能图谱
namediagnosing-bugs
descriptionDiagnosis loop for hard bugs and performance regressions. Use when the user says "diagnose"/"debug this", or reports something broken/throwing/failing/slow.
触发词针对棘手 bug 和性能回退的诊断循环。当用户说「诊断」/「调试这个」,或报告某些东西已损坏/抛异常/失败/缓慢时使用。

diagnosing-bugs

Diagnosis loop for hard bugs and performance regressions. Use when the user says "diagnose"/"debug this", or reports something broken/throwing/failing/slow.

A discipline for hard bugs. Skip phases only when explicitly justified.

针对棘手 bug 的一套方法论。只有明确理由时才跳过阶段。

When exploring the codebase, read CONTEXT.md (if it exists) to get a clear mental model of the relevant modules, and check ADRs in the area you're touching.

在查阅代码库时,先阅读 CONTEXT.md(如果存在)以建立相关模块的清晰心智模型,并检查你涉及区域的 ADR。

Phase 1 — Build a feedback loop

阶段 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.

这才是技能核心。 其他都只是机械步骤。如果你有一个针对该 bug 的紧凑的通过/失败信号——一个能在这个 bug 上变红的信号——你就会找到根因;二分法、假设检验、插桩都只是消耗这个信号而已。如果你没有这个信号,盯着代码看再多也无济于事。

Spend disproportionate effort here. Be aggressive. Be creative. Refuse to give up.

在此处投入不成比例的努力。要激进。要创造。拒绝放弃。

Ways to construct one — try them in roughly this order

构建反馈循环的方法——大致按此顺序尝试

  1. Failing test at whatever seam reaches the bug — unit, integration, e2e.
  2. Curl / HTTP script against a running dev server.
  3. CLI invocation with a fixture input, diffing stdout against a known-good snapshot.
  4. Headless browser script (Playwright / Puppeteer) — drives the UI, asserts on DOM/console/network.
  5. Replay a captured trace. Save a real network request / payload / event log to disk; replay it through the code path in isolation.
  6. Throwaway harness. Spin up a minimal subset of the system (one service, mocked deps) that exercises the bug code path with a single function call.
  7. Property / fuzz loop. If the bug is “sometimes wrong output”, run 1000 random inputs and look for the failure mode.
  8. Bisection harness. If the bug appeared between two known states (commit, dataset, version), automate “boot at state X, check, repeat” so you can git bisect run it.
  9. Differential loop. Run the same input through old-version vs new-version (or two configs) and diff outputs.
  10. HITL bash script. Last resort. If a human must click, drive them with scripts/hitl-loop.template.sh so the loop is still structured. Captured output feeds back to you.
  1. 失败的测试——在触及 bug 的任意接缝处——单元、集成、端到端。
  2. Curl / HTTP 脚本——针对运行中的开发服务器。
  3. CLI 调用——使用固定输入,将标准输出与已知正常的快照进行 diff。
  4. 无头浏览器脚本(Playwright / Puppeteer)——驱动 UI,对 DOM/控制台/网络进行断言。
  5. 回放捕获的跟踪。 将真实的网络请求/载荷/事件日志保存到磁盘;通过隔离的代码路径回放。
  6. 一次性测试架。 启动系统的最小子集(一个服务,模拟的依赖),用单个函数调用执行 bug 代码路径。
  7. 属性/模糊测试循环。 如果 bug 是「有时输出错误」,运行 1000 个随机输入并寻找失败模式。
  8. 二分测试架。 如果 bug 出现在两个已知状态(提交、数据集、版本)之间,自动化「在状态 X 启动、检查、重复」,以便可以 git bisect run 它。
  9. 差异循环。 对旧版本与新版本(或两种配置)运行相同输入,并对输出进行 diff。
  10. HITL bash 脚本。 最后手段。如果必须由人来点击,用 scripts/hitl-loop.template.sh 驱动他们,使循环仍然结构化。捕获的输出反馈给你。

Tighten the loop

收紧反馈循环

Treat the loop as a product. Once you have a loop, tighten it:

将反馈循环当作产品来对待。一旦你有了一个循环,就收紧它:

  • Can I make it faster? (Cache setup, skip unrelated init, narrow the test scope.)
  • Can I make the signal sharper? (Assert on the specific symptom, not “didn't crash”.)
  • Can I make it more deterministic? (Pin time, seed RNG, isolate filesystem, freeze network.)
  • 能更快吗?(缓存设置、跳过无关初始化、缩小测试范围。)
  • 信号能更清晰吗?(对具体症状断言,而不是「没崩溃」。)
  • 能更确定吗?(固定时间、设定随机种子、隔离文件系统、冻结网络。)

A 30-second flaky loop is barely better than no loop; a 2-second deterministic one is tight — a debugging superpower.

一个 30 秒的不稳定循环比没有循环好不到哪去;而一个 2 秒的确定性循环才是紧凑的——调试的超能力。

Non-deterministic bugs

非确定性 bug

The goal is not a clean repro but a higher reproduction rate. Loop the trigger 100×, parallelise, add stress, narrow timing windows, inject sleeps. A 50%-flake bug is debuggable; 1% is not — keep raising the rate until it's debuggable.

目标不是完美的复现,而是更高的复现率。将触发循环 100 次、并行化、增加压力、缩小时间窗口、注入睡眠。50% 不稳定率的 bug 是可调试的;1% 则不是——持续提高复现率直到可调试为止。

When you genuinely cannot build a loop

当确实无法构建反馈循环时

Stop and say so explicitly. List what you tried. Ask the user for: (a) access to whatever environment reproduces it, (b) a captured artifact (HAR file, log dump, core dump, screen recording with timestamps), or (c) permission to add temporary production instrumentation. Do not proceed to hypothesise without a loop.

停下来明确说明。列出你尝试过的方案。向用户请求:(a) 访问能复现的环境,(b) 捕获的产物(HAR 文件、日志转储、核心转储、带时间戳的屏幕录制),或 (c) 添加临时生产环境插桩的权限。不要在没有反馈循环的情况下继续假设。

Completion criterion — a tight loop that goes red

完成标准——一个能变红的紧凑循环

Phase 1 is done when the loop is tight and red-capable: you can name one command — a script path, a test invocation, a curl — that you have already run at least once (paste the invocation and its output), and that is:

当循环紧凑能变红时阶段 1 才算完成:你可以说出一个命令——脚本路径、测试调用、curl——你已经至少运行过一次(粘贴调用及其输出),并且它满足:

  • [ ] Red-capable — it drives the actual bug code path and asserts the user's exact symptom, so it can go red on this bug and green once fixed. Not “runs without erroring” — it must be able to catch this specific bug.
  • [ ] Deterministic — same verdict every run (flaky bugs: a pinned, high reproduction rate, per above).
  • [ ] Fast — seconds, not minutes.
  • [ ] Agent-runnable — you can run it unattended; a human in the loop only via scripts/hitl-loop.template.sh.
  • [ ] 能变红——驱动实际的 bug 代码路径,并对用户的确切症状进行断言,因此它能在这个 bug 上变红,修复后变绿。不是「运行时无错误」——它必须能捕获这个特定的 bug
  • [ ] 确定性的——每次运行结果一致(对于不稳定 bug:固定高复现率,如上所述)。
  • [ ] 快速——秒级,不是分钟级。
  • [ ] 代理可运行——你可以无人值守地运行它;只有通过 scripts/hitl-loop.template.sh 才需要人在循环中。

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.

如果你发现自己在这个命令存在之前就开始读代码构建理论,停下来——直接跳到假设正是这个技能要防止的错误。 没有能变红的命令,就没有阶段 2。

Phase 2 — Reproduce + minimise

阶段 2 — 复现 + 最小化

Run the loop. Watch it go red — the bug appears.

运行循环。看着它变红——bug 出现了。

Confirm:

确认:

  • [ ] The loop produces the failure mode the user described — not a different failure that happens to be nearby. Wrong bug = wrong fix.
  • [ ] The failure is reproducible across multiple runs (or, for non-deterministic bugs, reproducible at a high enough rate to debug against).
  • [ ] You have captured the exact symptom (error message, wrong output, slow timing) so later phases can verify the fix actually addresses it.
  • [ ] 循环产生了用户描述的失败模式——而不是恰好附近的另一个失败。错误的 bug = 错误的修复。
  • [ ] 失败在多次运行中可复现(或对于非确定性 bug,以足够高的比率可复现以便调试)。
  • [ ] 你已经捕获了确切的症状(错误消息、错误输出、缓慢的时间),以便后续阶段可以验证修复是否确实解决了问题。

Minimise

最小化

Once it's red, shrink the repro to the smallest scenario that still goes red. Cut inputs, callers, config, data, and steps one at a time, re-running the loop after each cut — keep only what's load-bearing for the failure.

一旦变红,将复现缩小到仍能变红的最小场景一次一个地减少输入、调用者、配置、数据和步骤,每次减少后重新运行循环——只保留对失败至关重要的部分。

Why bother: a minimal repro shrinks the hypothesis space in Phase 3 (fewer moving parts left to suspect) and becomes the clean regression test in Phase 5.

为什么要费这个劲:最小化复现缩小了阶段 3 中的假设空间(更少的活动部件值得怀疑),并成为阶段 5 中干净的回归测试。

Done when every remaining element is load-bearing — removing any one of them makes the loop go green.

每个剩余元素都是必要支撑时完成——移除其中任何一个都会使循环变绿。

Do not proceed until you have reproduced and minimised.

在完成复现最小化之前不要继续。

Phase 3 — Hypothesise

阶段 3 — 假设

Generate 3–5 ranked hypotheses before testing any of them. Single-hypothesis generation anchors on the first plausible idea.

在测试任何一个假设之前,先生成 3–5 个排序假设。生成单个假设会让你锚定在第一个看似合理的想法上。

Each hypothesis must be falsifiable: state the prediction it makes.

每个假设必须是可证伪的:陈述它做出的预测。

Format: “If <X> is the cause, then <changing Y> will make the bug disappear / <changing Z> will make it worse.”

格式:「如果 <X> 是原因,那么 <改变 Y> 会使 bug 消失 / <改变 Z> 会使它更严重。」

Show the ranked list to the user before testing. They often have domain knowledge that re-ranks instantly (“we just deployed a change to #3”), or know hypotheses they've already ruled out. Cheap checkpoint, big time saver. Don't block on it — proceed with your ranking if the user is AFK.

在测试之前将排序列表展示给用户。 他们通常拥有领域知识,可以立即重新排序(「我们刚刚部署了一个对 #3 的更改」),或者知道他们已经排除的假设。廉价的检查点,巨大的时间节省。不要阻塞——如果用户 AFK,就用你的排序继续。

Phase 4 — Instrument

阶段 4 — 插桩

Each probe must map to a specific prediction from Phase 3. Change one variable at a time.

每个探测必须对应阶段 3 中的具体预测。一次只改变一个变量。

Tool preference:

工具优先级:

  1. Debugger / REPL inspection if the env supports it. One breakpoint beats ten logs.
  2. Targeted logs at the boundaries that distinguish hypotheses.
  3. Never “log everything and grep”.
  1. 调试器 / REPL 检查——如果环境支持。一个断点胜过十条日志。
  2. 针对性日志——在区分假设的边界处。
  3. 永远不要「打出所有日志然后 grep」。

Tag every debug log with a unique prefix, e.g. [DEBUG-a4f2]. Cleanup at the end becomes a single grep. Untagged logs survive; tagged logs die.

为每条调试日志打上唯一前缀标签,例如 [DEBUG-a4f2]。最后的清理变成一个 grep 命令。未标记的日志存活;标记的日志被清除。

Perf branch. For performance regressions, logs are usually wrong. Instead: establish a baseline measurement (timing harness, performance.now(), profiler, query plan), then bisect. Measure first, fix second.

性能分支。 对于性能回退,日志通常是错的。相反:建立基线测量(计时测试架、performance.now()、性能分析器、查询计划),然后二分。先测量,后修复。

Phase 5 — Fix + regression test

阶段 5 — 修复 + 回归测试

Write the regression test before the fix — but only if there is a correct seam for it.

在修复之前编写回归测试——但前提是存在一个正确的接缝

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 (single-caller test when the bug needs multiple callers, unit test that can't replicate the chain that triggered the bug), a regression test there gives false confidence.

正确的接缝是指测试能按调用现场的实际方式,演练真实的 bug 模式。如果唯一可用的接缝太浅(bug 需要多个调用者时只有单调用者测试,单元测试无法复制触发 bug 的链条),在这样的接缝上写回归测试只会产生虚假的自信。

If no correct seam exists, that itself is the finding. Note it. The codebase architecture is preventing the bug from being locked down. Flag this for the next phase.

如果没有正确的接缝存在,这本身就是发现。 记下来。代码库架构阻止了 bug 被锁定。标记此问题供下一阶段处理。

If a correct seam exists:

如果正确的接缝存在:

  1. Turn the minimised repro into a failing test at that seam.
  2. Watch it fail.
  3. Apply the fix.
  4. Watch it pass.
  5. Re-run the Phase 1 feedback loop against the original (un-minimised) scenario.
  1. 将最小化复现转化为该接缝处的失败测试。
  2. 看着它失败。
  3. 应用修复。
  4. 看着它通过。
  5. 针对原始(未最小化的)场景重新运行阶段 1 的反馈循环。

Phase 6 — Cleanup + post-mortem

阶段 6 — 清理 + 事后分析

Required before declaring done:

在宣布完成前必须完成:

  • [ ] Original repro no longer reproduces (re-run the Phase 1 loop)
  • [ ] Regression test passes (or absence of seam is documented)
  • [ ] All [DEBUG-...] instrumentation removed (grep the prefix)
  • [ ] Throwaway prototypes deleted (or moved to a clearly-marked debug location)
  • [ ] The hypothesis that turned out correct is stated in the commit / PR message — so the next debugger learns
  • [ ] 原始复现不再重现(重新运行阶段 1 循环)
  • [ ] 回归测试通过(或接缝不存在已有记录)
  • [ ] 所有 [DEBUG-...] 插桩已移除(用前缀 grep
  • [ ] 一次性原型已删除(或移动到明确标记的调试位置)
  • [ ] 最终证实正确的假设已在提交/PR 消息中说明——以便下一个调试者学习

Then ask: what would have prevented this bug? If the answer involves architectural change (no good test seam, tangled callers, hidden coupling) hand off to the /improve-codebase-architecture skill with the specifics. Make the recommendation after the fix is in, not before — you have more information now than when you started.

然后问:什么可以预防这个 bug? 如果答案涉及架构变更(没有好的测试接缝、调用者纠结、隐藏的耦合),将具体信息移交给 /improve-codebase-architecture 技能。修复落地给出建议,而不是之前——你现在拥有的信息比开始时更多。