关于 diagnose
Diagnose 是一项系统排查疑难故障的技能,强调先建立快速、确定的反馈循环,再提出假设。之后按步骤复现故障、定向采集信息、添加回归测试并完成清理与复盘。
主要功能
- 01建立可重复的故障反馈循环
- 02复现并记录故障表现
- 03排列可证伪的假设
- 04进行定向信息采集
- 05在合适切入点添加回归测试
- 06检查清理与复盘事项
适用场景
- 01排查疑难或间歇性故障
- 02诊断性能回退
- 03依据原始故障验证修复
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ mattpocock/ skills --skill diagnose安装后在 Claude Code 中输入/diagnose确认已加载。
附带文件scripts/hitl-loop.template.sh
详细介绍
诊断
专门用于处理棘手 bug 的方法。只有在明确说明理由时,才能跳过阶段。
探索代码库时,使用项目的领域术语表,清楚了解相关模块,并查看你要修改的部分的 ADR。
阶段 1 — 建立反馈循环
这就是关键技能。 其他一切都是机械操作。如果你有一个快速、确定、可由 Agent 运行的通过/失败信号,就能找到原因——二分查找、假设检验和插桩都只是利用这个信号。如果没有,再怎么盯着代码看也无济于事。
在这里投入不成比例的精力。积极尝试。发挥创意。绝不轻易放弃。
构建反馈循环的方法 — 大致按以下顺序尝试
- 失败测试:在能够触及 bug 的任意边界上进行测试——单元测试、集成测试、端到端测试。
- Curl / HTTP 脚本:针对正在运行的开发服务器。
- CLI 调用:使用固定输入样例,并将标准输出与已知正常的快照进行差异比对。
- 无头浏览器脚本(Playwright / Puppeteer):驱动 UI,并对 DOM/控制台/网络进行断言。
- 重放捕获的跟踪记录。 将真实的网络请求/负载/事件日志保存到磁盘,然后通过隔离环境中的代码路径重放。
- 一次性测试框架。 启动系统的最小子集(一个服务、模拟依赖),通过一次函数调用触发有问题的代码路径。
- 属性/模糊测试循环。 如果 bug 表现为“输出有时错误”,就运行 1000 个随机输入,寻找故障模式。
- 二分测试框架。 如果 bug 出现在两个已知状态(提交、数据集、版本)之间,就自动执行“在状态 X 启动、检查、重复”,以便运行
git bisect run。 - 差分循环。 将相同输入分别交给旧版本和新版本(或两种配置),并比较输出。
- HITL bash 脚本。 最后的手段。如果必须由人点击,就用
scripts/hitl-loop.template.sh引导_他们_,让循环仍然有条理。捕获的输出会反馈给你。
构建合适的反馈循环,bug 就解决了 90%。
不断改进循环本身
把循环当作产品。建立_一个_循环后,思考:
- 能否让它更快?(缓存设置、跳过无关初始化、缩小测试范围。)
- 能否让信号更明确?(针对具体症状断言,而不是只检查“没有崩溃”。)
- 能否让它更确定?(固定时间、设置随机数种子、隔离文件系统、冻结网络。)
一个耗时 30 秒且不稳定的循环,几乎不比没有循环强。一个耗时 2 秒且确定的循环,是调试的超能力。
非确定性 bug
目标不是构造出干净的复现步骤,而是提高复现率。重复触发 100 次、并行运行、增加压力、缩小时间窗口、注入休眠。复现率为 50% 的 bug 可以调试;1% 的不行——持续提高复现率,直到能够调试。
确实无法构建循环时
停下来并明确说明。列出你尝试过的方法。向用户请求:(a) 访问能够复现问题的环境,(b) 捕获的产物(HAR 文件、日志转储、核心转储、带时间戳的屏幕录像),或 (c) 添加临时生产环境插桩的许可。不要在没有循环的情况下继续提出假设。
在建立你确信可靠的循环之前,不要进入阶段 2。
阶段 2 — 复现
运行循环。观察 bug 出现。
确认:
- 循环产生了用户描述的故障模式——而不是附近碰巧发生的另一种故障。找错 bug,就会修错问题。
- 多次运行都能复现故障(对于非确定性 bug,则需要以足够高的频率复现,才能据此调试)。
- 已捕获确切症状(错误消息、错误输出、耗时过长),以便后续阶段确认修复确实解决了问题。
在复现 bug 之前不要继续。
阶段 3 — 提出假设
在测试其中任何一个假设之前,生成 3–5 个按可能性排序的假设。只提出一个假设会让你锚定于第一个看似合理的想法。
每个假设都必须可证伪:明确写出它所预测的结果。
格式:“如果 是原因,那么 会使缺陷消失/ 会使缺陷恶化。”
如果无法给出预测,这个假设就只是一种直觉——应当舍弃或完善。
测试前先向用户展示排好序的假设列表。 用户通常掌握领域知识,往往能立刻重新排序(“我们刚部署了针对 #3 的更改”),或者知道哪些假设已经被排除。这只是成本极低的检查点,却能节省大量时间。不要等待回应——如果用户不在(AFK),就按你的排序继续。
阶段 4 — 埋点
每项探测都必须对应阶段 3 中的一项具体预测。每次只更改一个变量。
工具优先级:
- 调试器 / REPL 检查,前提是环境支持。一个断点胜过十条日志。
- 在能够区分各项假设的边界处添加有针对性的日志。
- 绝不要“记录所有内容再 grep”。
为每条调试日志添加标签,使用唯一前缀,例如 [DEBUG-a4f2]。这样,结束时只需执行一次 grep 即可完成清理。未加标签的日志留存;加了标签的日志淘汰。
性能分支。 对于性能回归,日志通常不是正确选择。应改为先建立基线测量(计时工具、performance.now()、性能分析器、查询计划),再进行二分排查。先测量,后修复。
阶段 5 — 修复 + 回归测试
先编写回归测试,然后再修复——但前提是存在一个正确的测试切入点。
正确的测试切入点,是指测试能够还原调用点处实际出现的缺陷模式。如果唯一可用的切入点过浅(例如,缺陷需要多个调用方,但测试只涉及单个调用方;或者单元测试无法复现触发缺陷的调用链),那么在该处进行回归测试只会带来虚假信心。
如果不存在正确的测试切入点,这本身就是调查结论。 记录这一点。当前代码库架构使该缺陷无法通过测试锁定。在下一阶段将这一点标出。
如果存在正确的测试切入点:
- 在该切入点将最小化后的复现转化为失败的测试。
- 确认测试失败。
- 应用修复。
- 确认测试通过。
- 针对原始的(未经最小化的)场景重新运行阶段 1 反馈循环。
阶段 6 — 清理 + 复盘
宣布完成前必须做到:
- 原始复现问题不再出现(重新运行阶段 1 循环)
- 回归测试通过(或已记录不存在测试切入点)
- 所有
[DEBUG-...]埋点均已移除(对该前缀执行grep) - 一次性原型已删除(或已移至明确标记的调试位置)
- 提交 / PR 消息中写明了最终证实正确的假设,让下一位调试者可以借鉴
然后问:怎样才能防止这个缺陷? 如果答案涉及架构变更(没有合适的测试切入点、调用关系混乱或存在隐蔽耦合),则将具体情况移交给 /improve-codebase-architecture 技能。应在修复完成后再提出建议,而不是提前提出——因为现在掌握的信息比开始时更多。