使用方法
- 复制安装命令,在自己的智能体环境安装前先检查源码包。
- 从原始文档核对所需工具、凭据和支持的宿主。
- 说明输入材料与期望交付物,并对照来源检查输出。
npx skills add https://github.com/ouyangjiahong26/skills --skill diagnosing-bugs开始前需了解
- 安装不包含模型订阅、API 额度或访问文件的权限。
- 仓库星数和安装量反映活跃情况,不等于独立质量或安全测试。
按源仓库内容呈现,保留标题、案例、代码、表格、链接以及原文引用的演示图片。
诊断 bug
一套对付难调 bug 的纪律。只有在明确说出理由时才能跳过阶段。
探索代码库时,先读 CONTEXT.md(若存在)建立相关模块的心智模型,并查你将要改动区域的 ADR。
脱敏
本 skill 会让你展示命令、输出和抓取到的产物。先给每个密钥脱敏:在它原位写 <REDACTED>。回路对着环境变量搭,让凭证留在环境里,而不是留在你展示的东西里。抓取的产物带 auth header:只引用承载信号的那几行。
如果脱敏后的输出不足以诊断 bug,说明情况,问用户。
阶段 1:建立反馈回路
这一步就是本 skill 的全部。 其余都是机械操作。如果你有一个紧的通过/失败信号(一个在这个 bug 上会变红的信号),你一定能找到根因;二分、验证假设、加打点,全都只是在用它。没有这样一个信号,盯着代码看到天亮也没用。
在这里投入不成比例的精力。积极点。有创意点。别放弃。
怎么搭一个回路:大致按这个顺序试
- 失败的测试,落在能触达 bug 的任意接口:单元、集成、端到端。
- curl / HTTP 脚本,打本地 dev server。
- CLI 调用,喂一个 fixture 输入,把 stdout 和一个已知正确的快照做 diff。
- 无头浏览器脚本(Playwright / Puppeteer):驱动 UI,对 DOM / console / network 做断言。
- 回放一条抓到的 trace。 把一个真实的网络请求 / payload / 事件日志存到磁盘,单独把它喂回代码路径。
- 一次性脚手架。 起一个系统的最小子集(一个服务、依赖打桩),用一次函数调用走通出 bug 的代码路径。
- 性质测试 / fuzz 回路。 如果 bug 是"偶尔输出错",跑 1000 个随机输入找失败模式。
- 二分脚手架。 如果 bug 出现在两个已知状态之间(commit、数据集、版本),把"启动到状态 X、检查、再来"自动化,好让
git bisect run接管。 - 差分回路。 同一输入跑老版本 vs 新版本(或两套配置),diff 输出。
- HITL bash 脚本。 最后手段。如果非得人手点,用
scripts/hitl-loop.template.sh驱动他们,让回路仍然有结构。抓到的输出再喂回给你。
收紧回路
把回路当成一个产品来对待。一旦有了一个回路,就收紧它:
- 能不能更快?(缓存 setup、跳过无关初始化、收窄测试范围。)
- 信号能不能更锋利?(断言具体的症状,而不是"没崩"。)
- 能不能更确定?(钉住时间、固定 RNG 种子、隔离文件系统、冻结网络。)
一个 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 的一条具体预测。一次只改一个变量。
工具优先级:
- 调试器 / REPL 检查,环境支持的话。一个断点抵十条 log。
- 针对性的 log,打在能区分各假设的边界上。
- 永不"什么都 log 再 grep 筛选"。
给每条 debug log 打一个唯一前缀,比如 [DEBUG-a4f2]。收尾时一次 grep 就能清干净。没打标签的 log 会活下来;打了标签的会死掉。
性能分支。 对性能回归,log 多半是错的。改成:建立基线测量(计时脚手架、performance.now()、profiler、query plan),然后二分。先量,再修。
阶段 5:修复 + 回归测试
先于修复写回归测试,但前提是存在一个正确的接口。
正确的接口,是测试能在调用点真正演习 bug 发生模式的接口。如果手头唯一的测试面太浅(bug 需要多个调用方、却只有单调用方测试;bug 需要整条调用链、单元测试却复现不了),在那里加回归测试只会给虚假信心。
如果没有正确的接口,这本身就是一条发现。 记下来。代码库的架构正在挡住这个 bug 被锁住。标给下一阶段。
存在正确的接口时:
- 把缩小后的复现变成那个接口上的失败测试。
- 看它挂。
- 上修复。
- 看它过。
- 用阶段 1 的回路,对着原始(未缩小的)场景再跑一遍。
阶段 6:收尾
宣布完成之前必做:
- [ ] 原始复现不再复现(重跑阶段 1 的回路)
- [ ] 回归测试通过(或:接口缺失已被记录)
- [ ] 所有
[DEBUG-...]打点已移除(grep前缀) - [ ] 一次性原型已删(或挪到一个明确标注的 debug 位置)
- [ ] 最终被验证为对的那条假设,写进了 commit / PR message:让下一个调它的人有得学




