# Diagnose

> Disciplined diagnosis loop for hard bugs and performance regressions. Reproduce → minimise → hypothesise → instrument → fix → regression-test. Use when user says "diagnose this" / "debug this", reports a bug, says something is broken/throwing/failing, or describes a performance regression.

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

---


# 诊断 / Diagnose

一套针对疑难 bug 的纪律。只有在有明确理由时才能跳过某个阶段。

在探索代码库时，使用项目的领域术语表来对相关模块建立清晰的心智模型，并查阅你正在改动区域的 ADR。

## 阶段 1 —— 构建反馈回路

**这才是真正的 skill。** 其余一切都是机械动作。如果你针对该 bug 拥有一个快速、确定性、能被 agent 运行的 pass/fail 信号，你就一定能找到原因 —— bisection、假设检验和 instrumentation 都只是消费这个信号。如果你没有这样的信号，再怎么盯着代码看也救不了你。

在这里投入不成比例的精力。**要积极。要有创造力。绝不放弃。**

### 构建方式 —— 大致按以下顺序尝试

1. 在能触及该 bug 的任意接缝上写一个**失败的测试** —— unit、integration、e2e 都行。
2. **Curl / HTTP 脚本**，针对正在运行的 dev server。
3. **CLI 调用**，使用一份 fixture 输入，将 stdout 与一份已知正确的快照做 diff。
4. **Headless 浏览器脚本**（Playwright / Puppeteer）—— 驱动 UI，对 DOM/console/network 做断言。
5. **回放捕获到的 trace。** 把一份真实的 network 请求 / payload / event log 存到磁盘上；在隔离环境中沿代码路径回放它。
6. **一次性 harness。** 启动系统的一个最小子集（一个 service，mock 掉的依赖），通过单次函数调用触发 bug 所在的代码路径。
7. **Property / fuzz 循环。** 如果 bug 是"有时输出错误"，就跑 1000 个随机输入，找出失败模式。
8. **Bisection harness。** 如果 bug 出现在两个已知状态之间（commit、数据集、版本），就把"在状态 X 启动、检查、重复"自动化，这样就能 `git bisect run` 它。
9. **Differential 循环。** 把同一个输入分别喂给旧版本和新版本（或两份配置），diff 输出结果。
10. **HITL bash 脚本。** 最后的手段。如果必须由人来点击，那就用 `scripts/hitl-loop.template.sh` 驱动 _他们_，让循环依然是结构化的。捕获到的输出再回流给你。

构建出正确的反馈回路，bug 就解决了 90%。

### 对回路本身进行迭代

把回路当作产品来对待。一旦你已经有了 _一个_ 回路，就要问：

- 我能让它更快吗？（缓存 setup、跳过无关的初始化、缩小测试范围。）
- 我能让信号更锐利吗？（针对具体症状做断言，而不是"没崩溃"。）
- 我能让它更确定吗？（固定时间、给 RNG 设种子、隔离 filesystem、冻结 network。）

一个 30 秒的 flaky 回路，比没有回路好不了多少。一个 2 秒的确定性回路就是调试超能力。

### 非确定性 bug

目标不是干净的复现，而是**更高的复现率**。把触发条件循环 100×，并行化，加压力，缩小时序窗口，注入 sleep。50% 复发率的 bug 是可调试的；1% 是不可调试的 —— 不停地把这个比例往上推，直到它变得可调试。

### 当你确实构不出回路时

停下来，明确说出这一点。列出你尝试过的方法。请用户提供：(a) 能复现该问题的环境的访问权限、(b) 一份捕获到的 artifact（HAR 文件、日志 dump、core dump、带时间戳的录屏），或 (c) 在生产环境临时添加 instrumentation 的许可。在没有回路的情况下，**不要**进入下一步去做假设。

在你信任自己手里的回路之前，不要进入阶段 2。

## 阶段 2 —— 复现

跑回路。看着 bug 出现。

确认：

- [ ] 回路产生的是**用户**所描述的那个失败模式 —— 而不是恰好就在附近的另一个失败。错的 bug = 错的 fix。
- [ ] 该失败在多次运行中都能复现（或者对于非确定性 bug 来说，复现率高到足以支撑调试）。
- [ ] 你已经捕获到了精确的症状（错误信息、错误输出、慢时序），以便后续阶段验证 fix 是否真的解决了它。

在你复现 bug 之前，不要继续往下走。

## 阶段 3 —— 假设

在动手测试之前，**生成 3–5 个有排序的假设**。只生成单一假设会把你锚定在第一个看起来合理的想法上。

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

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

如果你说不出预测，那这个假设就是一种感觉 —— 丢掉它或把它磨锐。

**在动手测试前把这份排序好的列表给用户看。** 他们经常拥有领域知识，能瞬间重新排序（"我们刚刚部署了一个针对 #3 的改动"），或者知道哪些假设已经被排除了。这是一个廉价的 checkpoint，能省下大把时间。不要为此阻塞 —— 如果用户 AFK，就按你的排序继续推进。

## 阶段 4 —— Instrument

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

工具偏好：

1. 如果环境支持，使用 **debugger / REPL 检查**。一个断点胜过十条日志。
2. 在能区分各假设的边界上打**有针对性的日志**。
3. 永远不要"全部打日志再 grep"。

**给每条调试日志都打上 tag**，使用唯一前缀，例如 `[DEBUG-a4f2]`。最后清理时只需要一次 grep。没打 tag 的日志会留下来；打了 tag 的日志会被清掉。

**性能分支。** 对于性能 regression，日志通常是错的方向。应该：先建立一个 baseline 测量（计时 harness、`performance.now()`、profiler、query plan），然后做 bisect。先测量，再修复。

## 阶段 5 —— 修复 + 回归测试

**先于 fix** 写回归测试 —— 但前提是存在一个**正确的接缝（seam）**。

正确的接缝指的是：测试在调用点上以**真实 bug 模式**触发它。如果唯一可用的接缝太浅（bug 需要多个 caller，但你只能写单 caller 测试；或者 unit test 无法复现触发 bug 的调用链），那么在那里写回归测试只会带来虚假的信心。

**如果不存在正确的接缝，这件事本身就是一个发现。** 把它记下来。代码库的架构在阻止这个 bug 被锁死。把这一点标记给下一个阶段。

如果存在正确的接缝：

1. 把已经最小化的 repro 转成那个接缝上的失败测试。
2. 看着它失败。
3. 应用 fix。
4. 看着它通过。
5. 在原始（未最小化）的场景上重新跑一遍阶段 1 的反馈回路。

## 阶段 6 —— 清理 + 复盘

宣布完成之前必须做：

- [ ] 原始 repro 不再复现（重新跑阶段 1 的回路）
- [ ] 回归测试通过（或者把"不存在接缝"这件事记录下来）
- [ ] 所有 `[DEBUG-...]` instrumentation 都被移除（`grep` 那个前缀）
- [ ] 一次性的 prototype 已删除（或挪到明确标记为调试用的位置）
- [ ] 最终被证实正确的那个假设被写在 commit / PR message 里 —— 这样下一个调试者就能学到

**然后问：什么样的改动本可以阻止这个 bug？** 如果答案涉及架构变更（没有好的测试接缝、调用方纠缠、隐藏的耦合），就带着具体细节交接给 `/improve-codebase-architecture` skill。把这条建议放在 fix 已经合入**之后**给出，而不是之前 —— 你现在掌握的信息比刚开始时多。

