Skill Learning

diagnosing-bugs

1. Bilingual SKILL.md

左右滑动可直接切换英文版与中文版;两种语言按段落自动对齐。英文中的蓝色虚线词语可点击查看解释。共标记 20 处。

入口 skill:diagnosing-bugs左右滑动切换语言 · 段落位置自动对齐
# Diagnosing Bugs
A discipline for hard bugs. Skip phases only when .
When exploring the codebase, read `CONTEXT.md` (if it exists) to get of the relevant modules, and check ADRs in the area you're touching.
## Redact
This skill has you show commands, outputs and captured artifacts. **Redact every secret first** — write `<REDACTED>` in its place. Build loops against env vars, so the credential stays in the environment rather than in what you show. Captured artifacts carry auth headers: .
If the redacted output is not enough to diagnose the bug, say so and ask the user.
## Phase 1 — Build a feedback loop
**This is the skill.** Everything else is mechanical. If you have a **tight** — 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, .
here. **Be aggressive. Be creative. Refuse to give up.**
### Ways to construct one — try them in roughly this order
1. **Failing test** at — unit, integration, e2e. 2. **Curl / HTTP script** against a running dev server. 3. **CLI invocation** with a fixture input, diffing stdout against a . 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.
Build the right feedback loop, and the bug is 90% fixed.
### 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.
### Non-deterministic bugs
The goal is not a clean repro but a ****. 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.
### 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 redacted 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.
### Completion criterion — a tight loop that goes red
Phase 1 is done when the loop is **tight** and ****: you can name **one command** — a script path, a test invocation, a curl — that you have **already run at least once** (show the invocation and its output, redacted), and that is:
- [ ] **** — 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`.
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 command, no Phase 2.
## Phase 2 — Reproduce + minimise
Run the loop. Watch it go red — the bug appears.
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.
### 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.
Done when **** — removing any one of them makes the loop go green.
Do not proceed until you have reproduced **and** minimised.
## Phase 3 — Hypothesise
Generate **3–5 ranked hypotheses** before testing any of them. Single-hypothesis generation .
Each hypothesis must be ****: 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."
If you cannot state the prediction, the hypothesis is a vibe — discard or sharpen it.
**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.
## Phase 4 — Instrument
Each probe must map to a specific prediction from Phase 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".
**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.
**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.
## Phase 5 — Fix + regression test
Write the regression test **before the fix** — but only if there is a **** for it.
A 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.
**If no 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.
If a 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.
## Phase 6 — Cleanup + post-mortem
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
**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
一套处理棘手 bug 的纪律。只有明确说明正当理由时,才能跳过阶段。
探索代码库时,应读取 `CONTEXT.md`(如果存在),以便对相关 module 建立清晰的心智模型;同时检查你正在修改区域内的 ADR。
## 脱敏
本 skill 会让你展示命令、输出和捕获的 artifact。**首先脱敏每一个 secret**——用 `<REDACTED>` 代替。反馈循环应通过 env var 使用凭据,使凭据留在环境中,而不是出现在你展示的内容里。捕获的 artifact 会携带 auth header:只引用真正包含信号的行。
如果脱敏后的输出不足以诊断 bug,应明确说明并向用户请求更多信息。
## 阶段 1——建立反馈循环
**这就是本 skill 的核心。** 其他一切都是机械步骤。如果你拥有一个针对该 bug 的**紧密**通过/失败信号——一个会在_这个_ bug 上变红的信号——你就能找到原因;二分、假设检验和 instrumentation 都只是在消费这个信号。如果没有,无论盯着代码看多久都救不了你。
在这里投入远高于平均比例的精力。**大胆。创造性地尝试。拒绝放弃。**
### 构造反馈循环的方法——大致按以下顺序尝试
1. 在任何能够触达 bug 的 seam 上编写**失败测试**——unit、integration 或 e2e。 2. 针对运行中的 dev server 编写 **Curl / HTTP script**。 3. 使用 fixture input 调用 **CLI**,并将 stdout 与 known-good snapshot 做 diff。 4. 编写**无头浏览器脚本**(Playwright / Puppeteer)——驱动 UI,并断言 DOM/console/network。 5. **重放捕获的 trace。** 将真实 network request / payload / event log 保存到磁盘;隔离地通过该代码路径重放。 6. **一次性 harness。** 启动系统的最小子集(一个 service、mocked dependency),用一次 function call 触发 bug 路径。 7. **Property / fuzz loop。** 如果 bug 是“输出偶尔错误”,就运行 1000 个随机输入并寻找该 failure mode。 8. **Bisection harness。** 如果 bug 出现在两个已知状态(commit、dataset、version)之间,就自动执行“在状态 X 启动、检查、重复”,以便使用 `git bisect run`。 9. **Differential loop。** 让同一个输入通过 old-version 与 new-version(或两种 config),再对输出做 diff。 10. **HITL bash script。** 最后的手段。如果必须由人点击,就用 `scripts/hitl-loop.template.sh` 驱动_人_,让循环仍保持结构化。捕获的输出会反馈给你。
建立正确的反馈循环,bug 就已经修好了 90%。
### 收紧反馈循环
把反馈循环当作一个 product。拥有_一个_循环后,要继续**收紧**它:
- 能否让它更快?(缓存 setup、跳过无关 init、缩小 test scope。) - 能否让信号更锐利?(断言具体症状,而不是“没有崩溃”。) - 能否让它更确定?(固定时间、固定 RNG seed、隔离 filesystem、冻结 network。)
一个耗时 30 秒且 flaky 的循环只比没有循环好一点;一个 2 秒、确定性的循环才算紧密——它是调试的超能力。
### 非确定性 bug
目标不是一次干净的复现,而是**更高的复现率**。将触发器循环 100 次、并行执行、增加压力、缩窄 timing window、注入 sleep。50% 的 flaky bug 可以调试;1% 的不行——持续提高复现率,直到它变得可调试。
### 当你确实无法建立反馈循环时
停止并明确说明。列出你尝试过的办法。向用户请求:(a) 可复现问题的环境访问权,(b) 已脱敏的捕获 artifact(HAR file、log dump、core dump、带时间戳的 screen recording),或 (c) 添加临时 production instrumentation 的许可。没有反馈循环时,**不要**继续提出假设。
### 完成标准——一个会变红的紧密反馈循环
当反馈循环既**紧密**又具备**变红能力**时,阶段 1 才算完成:你能说出**一条命令**——script path、test invocation 或 curl——而且你**已经至少运行过一次**(展示调用方式及其已脱敏输出),并且它满足:
- [ ] **具备变红能力(Red-capable)**——它驱动真实 bug 代码路径,并断言**用户的精确症状**,因此可以在该 bug 上变红、修复后变绿。不能只是“运行时没有报错”——它必须能够_捕获这个特定 bug_。 - [ ] **确定性(Deterministic)**——每次运行都给出相同 verdict(flaky bug:按上文固定一个高复现率)。 - [ ] **快速(Fast)**——以秒而非分钟计。 - [ ] **Agent 可运行(Agent-runnable)**——你可以无人值守地运行它;只有通过 `scripts/hitl-loop.template.sh` 才允许 human in the loop。
如果你发现自己在这条命令存在之前就开始读代码、构建理论,**停下来——直接跳到假设,正是这个 skill 要防止的错误。** 没有 red-capable 命令,就不能进入阶段 2。
## 阶段 2——复现 + 最小化
运行反馈循环。看着它变红——bug 出现了。
确认:
- [ ] 循环产生的是**用户**描述的 failure mode,而不是附近碰巧发生的另一个失败。错误的 bug = 错误的修复。 - [ ] 该失败能够在多次运行中复现(对于非确定性 bug,则要达到足够高、可用于调试的复现率)。 - [ ] 你已经捕获精确症状(error message、wrong output、slow timing),这样后续阶段才能验证修复确实解决了它。
### 最小化
变红后,把复现场景缩小为**仍然会变红的最小场景**。每次只削减一项 input、caller、config、data 或 step,每次削减后都重新运行循环——只保留对该失败不可或缺的部分。
为什么值得做:最小复现会缩小阶段 3 的假设空间(留下更少的可疑活动部件),并在阶段 5 中成为干净的 regression test。
当**每个剩余元素都不可或缺**时才算完成——删除其中任何一个,循环都会变绿。
在完成复现**和**最小化之前,不要继续。
## 阶段 3——提出假设
在测试任何假设之前,先生成 **3–5 个经过排序的假设**。只生成一个假设会让人锚定第一个看似合理的想法。
每个假设都必须**可证伪(falsifiable)**:明确写出它所做的预测。
> 格式:“如果 <X> 是原因,那么 <改变 Y> 会让 bug 消失 / <改变 Z> 会让它变得更严重。”
如果无法说出预测,这个假设就只是一种感觉——丢弃它,或把它变得更精确。
**测试前先向用户展示排序后的列表。** 用户往往拥有能瞬间改变排序的领域知识(“我们刚部署了与 #3 有关的变更”),或知道哪些假设已经被排除。这是便宜的 checkpoint,却能节省大量时间。不要让它阻塞流程——如果用户暂时不在线,就按你的排序继续。
## 阶段 4——添加 Instrumentation
每个 probe 都必须对应阶段 3 中的一个具体预测。**每次只改变一个变量。**
工具优先级:
1. 如果环境支持,优先使用 **Debugger / REPL inspection**。一个 breakpoint 胜过十条 log。 2. 在能够区分各假设的 boundary 添加**针对性 log**。 3. 绝不要“把所有东西都打到 log 里再 grep”。
用唯一 prefix **标记每一条 debug log**,例如 `[DEBUG-a4f2]`。这样结尾只需一次 grep 就能完成清理。未标记的 log 会残留;带标记的 log 必须删除。
**性能分支。** 对于性能回归,log 通常不是正确工具。应先建立 baseline measurement(timing harness、`performance.now()`、profiler、query plan),然后做二分。先测量,再修复。
## 阶段 5——修复 + 回归测试
在修复之前编写 regression test——但前提是存在一个**正确的 seam**。
正确的 seam 能让测试按照 bug 在 call site 中实际发生的方式,覆盖**真实 bug 模式**。如果唯一可用的 seam 太浅(bug 需要多个 caller,但测试只覆盖一个 caller;unit test 无法复现触发 bug 的调用链),那么在该处写 regression test 会制造虚假信心。
**如果不存在正确的 seam,这本身就是发现。** 把它记录下来。代码库架构正在阻止这个 bug 被可靠锁定。把这一点标记给下一阶段。
如果存在正确的 seam:
1. 在该 seam 上把最小复现转成 failing test。 2. 看着它失败。 3. 应用修复。 4. 看着它通过。 5. 用原始(未最小化)场景重新运行阶段 1 的反馈循环。
## 阶段 6——清理 + 事后复盘
在宣布完成之前必须做到:
- [ ] 原始复现不再出现(重新运行阶段 1 的循环) - [ ] Regression test 通过(或者已经记录 seam 缺失) - [ ] 删除所有 `[DEBUG-...]` instrumentation(grep 该 prefix) - [ ] 删除一次性 prototype(或移动到明确标注的 debug location) - [ ] 在 commit / PR message 中写明最终被证实的正确假设——让下一位调试者能够学习
**然后追问:什么本可以防止这个 bug?** 如果答案涉及架构变更(缺少良好 test seam、caller 纠缠、隐藏 coupling),就带着具体信息 hand off 给 `/improve-codebase-architecture` skill。应在修复完成**之后**提出建议,而不是之前——此时你掌握的信息比开始时更多。

Entry SHA-256: b9339b09ee3980808d8c5a35c7251b891b8b1e0036ec4ca37812b976ebddf6b6

2. Why This Skill Is Clever

一句话核心机制

先建立一个已运行、能精准捕获用户症状的快速红/绿反馈回路,再让复现、最小化、假设、探针和修复全部消费同一信号。

触发与边界

Direct source: frontmatter 把触发面写成 "diagnose"/"debug this",以及 broken、throwing、failing、slow 等具体故障描述。正文则把它限定为 “A discipline for hard bugs”,说明这是针对难查缺陷和性能回归的重型流程,而不是一般性的代码问答或无症状的性能巡检。

Supporting context: agents/openai.yaml 只提供显示名和简短描述;它没有把该 skill 设为仅人工调用。因此是否由模型自动触发,仍依赖具体 harness 的发现与调用机制。

Interpretation: 触发词覆盖面很宽,而流程刻意很重。在自动调用阈值较低的 harness 中,它可能对简单报错过度响应;迁移时最好增加“困难、可复现的具体症状”这一门槛,或允许用户明确要求轻量回答。

信息架构与执行顺序

  1. 先保护信息边界。 Redact every secret first 位于全部技术阶段之前,确保后续展示命令、输出和捕获物时不泄露凭据。
  2. Phase 1 建立信号。 从 failing test 到 HITL script,按大致优先级寻找可运行的反馈回路,并把它收紧到 fast、deterministic、agent-runnable、red-capable。
  3. Phase 2 缩小问题。 先确认复现的是用户报告的同一个故障,再逐项删除非必要因素,直到每个剩余元素都 load-bearing
  4. Phase 3–4 用实验消除不确定性。 先生成 3–5 个可证伪且有预测的排序假设,再让每个 probe 对应一个预测,并坚持一次只改一个变量。
  5. Phase 5–6 闭环。 在正确接缝上先写回归测试、应用修复、重跑原始场景,最后删除调试产物并把正确假设写进 commit / PR message。

最巧妙的设计点

1. 把反馈回路定义为产品,而不是临时命令

“Treat the loop as a product.”

Direct source: skill 不满足于“能复现”,还要求继续优化速度、信号锐度与确定性。完成标准甚至要求给出一条已经运行过的具体命令和经过脱敏的输出。

Interpretation: 这把调试基础设施变成了可迭代资产。后续每次最小化、探针和修复都能廉价重跑,减少人工记忆与偶然观察带来的误判。

2. 用“红能力”阻止绿色幻觉

“Not ‘runs without erroring’”

Direct source: red-capable 要求命令能在该缺陷存在时抓住用户的精确症状,而不只是正常退出。

Interpretation: 这是对测试最常见误区的精准修正:一个永远为绿的检查无法证明修复有效。先证明检测器能失败,才有资格相信它之后的成功。

3. 把偶发问题改写成概率工程

“The goal is not a clean repro but a higher reproduction rate.”

Direct source: 对 flaky bug,策略是循环、并行、施压、缩窄时序窗口和注入 sleep,把 1% 的触发率推高到可调试水平。

Interpretation: 它避免把“不能稳定复现”误判为“无法调查”,而是先优化观测概率,再进入因果分析。

4. 先生成多个预测,再允许探针进入代码

“Generate 3–5 ranked hypotheses”

Direct source: 每个假设必须可证伪,并用 “If X… then changing Y…” 格式明确预测;假设列表还要先展示给用户,但用户离线时不阻塞执行。

Interpretation: 排序控制成本,可证伪控制质量,用户 checkpoint 则低成本注入领域知识。三者共同对抗“第一个合理解释”的锚定偏差。

5. 拒绝为了回归测试而写错误的测试

“If no correct seam exists, that itself is the finding.”

Direct source: 如果浅层测试无法重现真实调用模式,skill 宁愿记录架构缺口,也不接受会产生虚假信心的测试。

Interpretation: 这是少见但重要的诚实边界:测试数量不是目标,证据保真度才是。缺少正确接缝会在修复后转化为架构改进的具体输入。

6. 清理规则被设计成可机械验证

“Tag every debug log with a unique prefix”

Direct source: 所有临时日志必须带 [DEBUG-a4f2] 一类唯一前缀,结尾用一次 grep 验证它们全部消失。

Interpretation: 与其依赖“记得删日志”,不如提前给临时产物统一可搜索身份。这是把清理从提醒变成可执行 invariant。

防失败机制与 trade-off

显式 invariants(Direct source):

Trade-off 与可移植风险(Interpretation):

可迁移原则

  1. 先设计检测器,再设计答案。 任何修复流程都应先证明自己的判据既能失败也能成功。
  2. 让阶段由证据开门。 用可检查的完成条件替代“看起来差不多了”。
  3. 把不确定性写成预测。 每个假设都要说明什么观察会推翻它。
  4. 给临时产物可搜索身份。 日志、feature flag、debug file 都应能一次性检索和清理。
  5. 拒绝低保真证明。 如果测试接缝不能覆盖真实模式,记录架构缺口比制造绿色结果更可靠。

今日实践题

你正在排查一个“偶尔重复扣款”的问题:目前只有一条日志说请求成功。请写出一条真正 red-capable 的反馈回路应该断言的用户精确症状,并说明怎样把复现率从 1% 提高到可调试水平。

Evidence note

本文的阶段、门槛、短引文和安全规则均来自所选 SKILL.md(Direct source);agents/openai.yamlscripts/hitl-loop.template.sh 只用于解释 harness 展示信息和人工回路机制(Supporting context)。关于调用过重、概率工程、可移植性与设计价值的判断属于分析(Interpretation),不是原文声称的事实。

Source & provenance
Repository path
skills/engineering/diagnosing-bugs/SKILL.md
Generated
2026-08-13 09:17 Asia/Shanghai
Upstream commit
84fdeffd12f2ee307994d1eb6feb48173b6e0502
Source
View on GitHub