mcpField
D
diagnosing-bugs

diagnosing-bugs

github.com/ouyangjiahong26Development580 installsUpdated 1d ago
Repository

About diagnosing-bugs

A structured skill for debugging difficult bugs, aimed at coding agents and developers. It requires a reproducible, tight feedback loop before moving through reduction, hypothesis testing, repair, and verification.

Key features

  1. 01Build a failing feedback loop
  2. 02Reduce bugs to minimal reproductions
  3. 03Rank falsifiable hypotheses
  4. 04Instrument targeted diagnostic points
  5. 05Add regression tests and verify fixes
  6. 06Remove debug artifacts during cleanup

Use cases

  1. 01Diagnose bugs with repeatable symptoms
  2. 02Investigate intermittent failures
  3. 03Trace problems across service boundaries

Install

Copy the command and run it in a terminal.

$npx skills add https://github.com/ouyangjiahong26/skills --skill diagnosing-bugs

After installing, type/diagnosing-bugsin Claude Code to confirm it loaded.

Bundled filesscripts/hitl-loop.template.sh

Details

From the project README

诊断 bug

一套处理难调 bug 的纪律。只有明确说出理由时才能跳过阶段。

探索代码库时,先读 CONTEXT.md(若存在)建立相关模块的心智模型,并查你将要改动区域的 ADR。

脱敏

本 skill 会让你展示命令、输出和抓取到的产物。先给每个密钥脱敏:在原来的位置写 <REDACTED>。回路基于环境变量搭建,让凭证留在环境里,而不是留在你展示的内容里。抓取的产物带 auth header:只引用承载信号的那几行。

如果脱敏后的输出不足以诊断 bug,说明情况,问用户。

阶段 1:建立反馈回路

这一步就是本 skill 的全部。其余都是机械操作。你有一个紧的通过/失败信号(一个在这个 bug 上会失败的信号),就一定能找到根因。二分、验证假设、加打点,全都只是在用它。没有这样一个信号,盯着代码看到天亮也没用。

在这里投入不成比例的精力。积极点。有创意点。别放弃。

怎么搭一个回路:大致按这个顺序试
  1. 失败的测试,落在能触达 bug 的任意接口:单元、集成、端到端。
  2. curl / HTTP 脚本,打本地 dev server。
  3. CLI 调用,喂一个 fixture 输入,把 stdout 和一个已知正确的快照做 diff。
  4. 无头浏览器脚本(Playwright / Puppeteer):驱动 UI,对 DOM / console / network 做断言。
  5. 回放一条抓到的 trace:把一个真实的网络请求 / payload / 事件日志存到磁盘,单独喂回代码路径。
  6. 一次性脚手架:起一个系统的最小子集(一个服务、依赖打桩),用一次函数调用走通出 bug 的代码路径。
  7. 性质测试 / fuzz 回路:如果 bug 是“偶尔输出错”,跑 1000 个随机输入找失败模式。
  8. 二分脚手架:如果 bug 出现在两个已知状态之间(commit、数据集、版本),把“启动到状态 X、检查、再来”自动化,好让 git bisect run 接管。
  9. 差分回路:同一输入分别跑老版本和新版本(或两套配置),diff 输出。
  10. HITL bash 脚本:最后手段。如果非得人手点,用 scripts/hitl-loop.template.sh 驱动他们,让回路仍然有结构。抓到的输出再喂回给你。
收紧回路

把回路当成一个产品来对待。一旦有了一个回路,就收紧它:

  • 能不能更快?(缓存 setup、跳过无关初始化、收窄测试范围。)
  • 信号能不能更锋利?(断言具体的症状,而不是“没崩”。)
  • 能不能更确定?(固定时钟、固定随机种子、隔离文件系统、冻结网络。)

一个 30 秒还偶发失败的回路,比没回路好不了多少。一个 2 秒、每次结果都确定的回路才是紧的,也就是调试的超能力。

非确定性 bug

目标不是一次干净的复现,而是更高的复现率。把触发条件跑 100 遍,并行跑,加压力,收窄时序窗口,注入 sleep。50% 偶发的 bug 还能调,1% 的不能,要持续把复现率拉高,直到能调为止。

真搭不出回路时

停下来,说明情况。列出你已经试过的。向用户要:(a) 能复现它的那个环境的访问权限,(b) 一份脱敏的抓取产物(HAR 文件、日志转储、core dump、带时间戳的录屏),或 (c) 在生产环境加临时打点的许可。没有回路时,不要进入假设阶段。

完成条件:一个会失败的紧回路

阶段 1 在回路既紧又会失败时结束:你能说出一条命令(一个脚本路径、一次测试调用、一条 curl),至少跑过一次(贴出调用和脱敏后的输出),并且它满足:

  • 会失败:走的是 bug 真正的代码路径,断言的是用户原话描述的症状,所以它能在这个 bug 上失败,修好后通过。不是“跑起来不报错”,必须能逮住这个具体的 bug。
  • 确定:每次跑结果一致(偶发 bug:按上面说的,固定一个高复现率)。
  • 快:秒级,不是分钟级。
  • agent 能跑:能无人值守地跑。人介入只通过 scripts/hitl-loop.template.sh。

如果这条命令还不存在,你却开始读代码、构建理论,停下:跳过回路直接跳到假设,正是本 skill 要防止的失败模式。没有会失败的命令,就没有阶段 2。

阶段 2:复现 + 缩小

跑回路。看它失败:bug 出现了。

确认:

  • 回路产出的是用户描述的那个失败模式,不是恰好挨着的另一个失败。搞错 bug 就是修错地方。
  • 多次跑都能复现(或:偶发 bug 以足够高的复现率出现,能拿来调)。
  • 已经抓下了精确的症状(错误信息、错误输出、慢的耗时),后面的阶段才能验证修复确实对上了症状。
缩小

一旦失败,把复现缩到仍然会失败的最小场景。一次砍掉一项(输入、调用方、配置、数据、步骤),每砍一项就重跑一次回路,只留下对失败真正承重的。

为什么要费这个劲:最小复现缩小了阶段 3 的假设空间(剩下的可疑部件更少),并在阶段 5 成为干净的回归测试。

缩小完成的标志是剩下的每一项都承重:去掉任何一项,回路都会通过。

在复现并缩小之前,不要往下走。

阶段 3:假设

测试之前,先给出 3 到 5 条排好序的假设。只生成一条假设,就会被锚在第一个听起来靠谱的念头上。

每条假设必须可证伪:能说出它做出的预测。

格式:“如果 <X> 是根因,那么 <改 Y> 会让 bug 消失 / <改 Z> 会让它更严重。”

说不清预测,这条假设就只是个感觉:丢掉,或磨利它。

测试之前,把排好序的清单给用户看一眼。他们常有领域知识,能立刻重排(“我们刚把 #3 那块部署上去了”),或知道哪些假设已经被排除。这是一次便宜的检查点,能省下大把时间。用户不在时不要等,按你的排序继续。

阶段 4:打点

每一处探针对应阶段 3 的一条具体预测。一次只改一个变量。

工具优先级:

  1. 调试器 / REPL 检查,环境支持时用。一个断点抵十条 log。
  2. 针对性的 log,打在能区分各假设的边界上。
  3. 永远不要“什么都 log 再 grep 筛选”。

给每条 debug log 打一个唯一前缀,比如 [DEBUG-a4f2]。收尾时一次 grep 就能清干净。没打标签的 log 会留下来,打了标签的会被清掉。

性能分支:对性能回归,log 多半不是对的办法。改成先建立基线测量(计时脚手架、performance.now()、profiler、query plan),固定要看的指标(如 P95 延迟、分配次数、查询耗时)和可判定阈值,再用同一指标二分。先量,再修。

分布式分支:跨服务 bug 的打点放在服务边界,给请求注入同一个 trace id 并让它沿调用链传播。在每个服务的入口与出口打点(收到、发出、耗时、状态)。按 trace id 把各服务日志汇成一条时间线,再对照假设。跨服务比较时间戳前先对齐时钟源,或只比较同一服务内的相对耗时。线上抓不到流量时,用阶段 1 的回放回路重放一条抓取到的请求。

阶段 5:修复 + 回归测试

在修复之前写回归测试,但前提是存在一个正确的接口。

正确的接口,是测试能在调用点真正演练 bug 发生模式的接口。如果手头唯一的测试面太浅(bug 需要多个调用方,却只有单调用方的测试。或者 bug 需要整条调用链,单元测试却复现不了),在那里加回归测试只会带来虚假的信心。

如果没有正确的接口,这本身就是一条发现。记下来。代码库的架构正在妨碍用回归测试锁住这个 bug。标给下一阶段。

存在正确的接口时:

  1. 把缩小后的复现变成那个接口上的失败测试。
  2. 看它失败。
  3. 应用修复。
  4. 看它通过。
  5. 用阶段 1 的回路,对着原始(未缩小的)场景再跑一遍。

阶段 6:收尾

宣布完成之前必做:

  • 原始复现不再复现(重跑阶段 1 的回路)
  • 回归测试通过(或:接口缺失已被记录)
  • 所有 [DEBUG-...] 打点已移除(grep 前缀)
  • 一次性原型已删(或挪到一个明确标注的 debug 位置)
  • 最终验证成立的那条假设写进了 commit / PR message,下一个调它的人能学到东西

Comments

Markdown supported