# Diagnosing Bugs

> 针对棘手的 bug 和性能 regression 的诊断回路。当用户说 "diagnose"/"debug this",或报告某样东西 broken/throwing/failing/slow 时使用。

- Skill: `gongyijie85/diagnosing-bugs` (Agent Skill)
- Install (CLI): `npx skillmds@latest add gongyijie85/diagnosing-bugs`
- Raw SKILL.md: https://api.skillmd.com/api/skills/gongyijie85/diagnosing-bugs/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: gongyijie85 (https://skillmd.com/u/gongyijie85)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/gongyijie85/diagnosing-bugs

---


# Diagnosing Bugs

针对棘手 bug 的纪律。只有在明确有理有据时才跳过某些 phase。

探索 codebase 时,阅读 `CONTEXT.md`(如果存在)以获得相关模块清晰的 mental model(心智模型),并检查你正在接触区域的 ADR。

## Redact(脱敏)

本 skill 要求你展示命令、输出和捕获的产物。**先对每个 secret 脱敏** — 用 `<REDACTED>` 代替。针对 env vars 构建回路,这样凭据留在环境中,而不是留在你展示的内容里。捕获的产物带有 auth headers:只引用携带信号的几行。

如果脱敏后的输出不足以诊断 bug,直说并询问用户。

## Phase 1 — 构建反馈回路

**这才是本 skill 的核心。** 其他一切都是机械性的。如果你有一个针对该 bug 的**紧致**通过/失败信号 — 一个能在_这个_ bug 上变红的信号 — 你就会找到原因;bisection(二分)、假设检验和 instrumentation 都只是在消费它。如果你没有这样的信号,再怎么盯着代码看也救不了你。

在这里投入不成比例的努力。**要激进。要有创造力。拒绝放弃。**

### 构建反馈回路的方法 — 大致按这个顺序尝试

1. **失败的测试** — 在任何能触达 bug 的 seam 上 — unit、integration、e2e。
2. **Curl / HTTP 脚本** — 针对运行中的 dev server。
3. **CLI 调用** — 用 fixture 输入,把 stdout 与已知良好的快照做 diff。
4. **无头浏览器脚本**(Playwright / Puppeteer)— 驱动 UI,在 DOM/console/network 上做断言。
5. **重放捕获的 trace。** 把真实的网络请求 / payload / 事件日志保存到磁盘;在隔离环境中通过代码路径重放它。
6. **一次性 harness。** 启动系统的一个最小子集(一个 service,依赖用 mock),用一次函数调用触发 bug 代码路径。
7. **Property / fuzz 回路。** 如果 bug 是 "sometimes wrong output(偶尔输出错误)",运行 1000 个随机输入,寻找失败模式。
8. **Bisection harness。** 如果 bug 出现在两个已知状态(commit、数据集、版本)之间,把 "boot at state X, check, repeat(在状态 X 启动、检查、重复)" 自动化,这样你就可以对它 `git bisect run`。
9. **差分回路(Differential loop)。** 让同一输入分别通过旧版本和新版本(或两种配置),对输出做 diff。
10. **HITL bash 脚本。** 最后的手段。如果必须由人来点击,用 `scripts/hitl-loop.template.sh` 驱动_他们_,这样回路仍然是有结构的。捕获的输出反馈给你。

构建出正确的反馈回路,bug 就修好了 90%。

### 收紧回路

把回路当作产品来对待。一旦你有了_一条_回路,就**收紧**它:

- 我能让它更快吗?(缓存 setup、跳过无关的初始化、缩小测试范围。)
- 我能让信号更锐利吗?(对具体症状做断言,而不是 "didn't crash(没崩溃)"。)
- 我能让它更确定吗?(固定时间、给 RNG 播种、隔离文件系统、冻结网络。)

一个 30 秒的 flaky 回路和没有回路相比几乎毫无优势;一个 2 秒的确定性回路才是紧致的 — 那是调试的超能力。

### 非确定性 bug

目标不是干净的 repro(复现),而是**更高的复现率**。把触发器循环 100 次、并行化、加压、收窄时间窗口、注入 sleep。50% 概率 flake 的 bug 是可调试的;1% 的不是 — 不断提高复现率,直到它可调试。

### 当你确实无法构建回路时

停下来,明确说明。列出你尝试过的东西。向用户请求:(a) 访问能复现它的环境,(b) 一份脱敏的捕获产物(HAR 文件、日志转储、core dump、带时间戳的屏幕录制),或 (c) 添加临时生产 instrumentation 的许可。**不要**在没有回路的情况下继续假设。

### 完成标准 — 一条能变红的紧致回路

当回路**紧致**且**能变红(red-capable)**时,Phase 1 就完成了:你能说出**一条命令** — 一个脚本路径、一次测试调用、一条 curl — 而且你已经**至少运行过一次**(展示这条调用及其输出,已脱敏),并且它:

- [ ] **Red-capable(能变红)** — 它驱动真实的 bug 代码路径,并对**用户的确切症状**做断言,因此它能在这个 bug 上变红,修复后变绿。不是 "runs without erroring(运行不报错)" — 它必须能够_抓住这个具体的 bug_。
- [ ] **Deterministic(确定性)** — 每次运行结果一致(flaky bug:按上文,固定一个高复现率)。
- [ ] **Fast(快速)** — 秒级,而不是分钟级。
- [ ] **Agent-runnable(agent 可运行)** — 你可以无人值守地运行它;只有通过 `scripts/hitl-loop.template.sh` 才有人类在环。

如果你发现自己在这条命令存在之前就开始读代码构建理论,**停下来 — 直接跳到假设正是本 skill 要阻止的失败。** 没有能变红的命令,就没有 Phase 2。

## Phase 2 — 复现 + 最小化

运行回路。看着它变红 — bug 出现了。

确认:

- [ ] 回路产生**用户**描述的失败模式 — 而不是恰好邻近的另一个失败。找错 bug = 修错东西。
- [ ] 失败可以跨多次运行复现(或者,对于非确定性 bug,复现率足够高,足以作为调试依据)。
- [ ] 你已经捕获确切症状(错误消息、错误输出、缓慢的耗时),这样后续 phase 可以验证修复确实解决了它。

### 最小化

一旦它变红,把 repro 缩小到**仍然变红的最小场景**。**一次一个**地削减输入、调用者、配置、数据和步骤,每次削减后重新运行回路 — 只保留对失败起承重作用的东西。

为什么要费这个劲:最小化的 repro 缩小了 Phase 3 中的假设空间(剩下可怀疑的活动部件更少),并在 Phase 5 中变成干净的 regression test。

当**每个剩余元素都是承重的**时完成 — 移除其中任何一个都会让回路变绿。

在完成复现**并且**最小化之前,不要继续。

## Phase 3 — 提出假设

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

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

> 格式:"如果 <X> 是原因,那么 <改变 Y> 会让 bug 消失 / <改变 Z> 会让它更糟。"

如果你无法陈述预测,这个假设只是一种感觉(vibe)— 丢弃它或把它打磨锋利。

**在测试之前把排序后的列表展示给用户。** 他们通常拥有能瞬间重新排序的 domain 知识("我们刚部署了一个对 #3 的改动"),或者知道他们已经排除的假设。便宜的检查点,巨大的时间节省。不要被它阻塞 — 如果用户不在(AFK),就按你的排序继续。

## Phase 4 — 插桩(Instrument)

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

工具偏好:

1. **Debugger / REPL 检查** — 如果环境支持的话。一个断点胜过十条日志。
2. **定向日志** — 放在能区分假设的边界处。
3. 绝不 "log everything and grep(什么都打日志然后 grep)"。

**给每条 debug 日志打上唯一前缀标签**,例如 `[DEBUG-a4f2]`。最后的清理就变成一次 grep。没打标签的日志存活;打了标签的日志被清除。

**Perf 分支。** 对于性能 regression,日志通常是错的。替代方案:先建立基线测量(计时 harness、`performance.now()`、profiler、query plan),然后二分。先测量,后修复。

## Phase 5 — 修复 + regression test

在**修复之前**写 regression test — 但前提是存在一个**正确的 seam**。

正确的 seam 是指测试能在调用点按 bug 实际发生的方式驱动**真实的 bug 模式**。如果唯一可用的 seam 太浅(当 bug 需要多个调用者时只有一个单调用者测试、无法复现触发 bug 的调用链的 unit test),那里的 regression test 只会带来虚假的信心。

**如果不存在正确的 seam,这本身就是发现。** 把它记下来。是 codebase 架构阻止了 bug 被锁死。为下一个 phase 标记这一点。

如果存在正确的 seam:

1. 把最小化的 repro 变成那个 seam 上的失败测试。
2. 看着它失败。
3. 应用修复。
4. 看着它通过。
5. 针对原始的(未最小化的)场景重新运行 Phase 1 反馈回路。

## Phase 6 — 清理

在宣布完成之前必须做到:

- [ ] 原始 repro 不再复现(重新运行 Phase 1 回路)
- [ ] Regression test 通过(或缺少 seam 的情况已被记录)
- [ ] 所有 `[DEBUG-...]` instrumentation 已被移除(`grep` 该前缀)
- [ ] 一次性 prototypes 已删除(或移到明确标记的 debug 位置)
- [ ] 被证明正确的假设写进了 commit / PR message — 这样下一位调试者能学到

