mcpField
D
diagnosing-bugs

diagnosing-bugs

github.com/c0nant开发336 次安装2 周前更新
代码仓库

关于 diagnosing-bugs

面向复杂缺陷排查的调试技能,要求先建立能稳定复现并检测具体症状的反馈循环,再逐步缩小问题、检验假设并修复。它强调可重复验证、针对性探测和回归测试,并要求清理调试痕迹。

主要功能

  1. 01先建立针对症状的反馈循环
  2. 02复现并缩小故障场景
  3. 03提出可证伪的排序假设
  4. 04按假设进行定向探测
  5. 05修复并添加回归测试
  6. 06清理调试日志和临时文件

适用场景

  1. 01排查难以定位的代码缺陷
  2. 02分析非确定性故障
  3. 03定位性能回退
  4. 04将最小复现转为回归测试

安装

复制下面的命令,在终端中运行。

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

安装后在 Claude Code 中输入/diagnosing-bugs确认已加载。

附带文件agents/openai.yamlscripts/hitl-loop.template.sh

详细介绍

诊断 Bug

一种处理难以解决的 Bug 的方法。只有在明确说明理由时,才跳过阶段。

探索代码库时,阅读 CONTEXT.md(如果存在),以清楚了解相关模块;同时检查你要修改区域的 ADR。

脱敏

这项技能要求你展示命令、输出和捕获的工件。务必先对所有机密信息进行脱敏:用 <REDACTED> 替代。针对环境变量编写循环,让凭据留在环境中,而不是出现在你展示的内容里。捕获的工件可能包含身份验证标头:只引用包含关键信息的行。

如果脱敏后的输出不足以诊断 Bug,请说明这一点并询问用户。

阶段 1:构建反馈循环

这就是这项技能的核心。 其他一切都是机械步骤。如果你有一个针对 Bug 的紧凑通过/失败信号(遇到_这个_ Bug 时会失败),就能找到原因;二分查找、假设检验和检测手段都只是利用它。如果没有这样的信号,再怎么盯着代码看也无济于事。

在这里投入远超常规的精力。积极尝试,勇于创新,不要轻易放弃。

构建反馈循环的方法,大致按优先顺序排列
  1. 失败的测试:在能够触达 Bug 的任意边界上测试,包括单元测试、集成测试和端到端测试。
  2. Curl/HTTP 脚本:针对正在运行的开发服务。
  3. CLI 调用:使用测试输入,并将标准输出与已知正确的快照进行差异比较。
  4. 无头浏览器脚本(Playwright/Puppeteer):驱动 UI,并对 DOM/控制台/网络进行断言。
  5. 重放捕获的跟踪记录。 将真实网络请求/负载/事件日志保存到磁盘,再通过隔离环境中的代码路径重放。
  6. 临时测试框架。 启动系统的最小子集(一个服务、模拟依赖项),通过一次函数调用来触发包含 Bug 的代码路径。
  7. 属性/模糊测试循环。 如果 Bug 表现为“输出有时不正确”,就运行 1000 个随机输入并查找故障模式。
  8. 二分查找测试框架。 如果 Bug 出现在两个已知状态(提交、数据集、版本)之间,就自动执行“在状态 X 启动,检查,重复”,以便运行 git bisect run。
  9. 差异测试循环。 将同一输入分别通过旧版本和新版本(或两种配置)运行,并比较输出差异。
  10. 人工参与的 Bash 脚本。 最后的手段。如果必须由人点击,就用 scripts/hitl-loop.template.sh 引导_他们_,让循环依然有章可循。捕获的输出会反馈给你。

构建合适的反馈循环,Bug 就解决了 90%。

缩短循环

将循环视为产品。有了_一个_循环后,就要设法缩短它:

  • 能否让它更快?(缓存初始化、跳过无关的初始化、缩小测试范围。)
  • 能否让信号更明确?(针对具体症状进行断言,而不是只判断“没有崩溃”。)
  • 能否让它更确定?(固定时间、设置随机数种子、隔离文件系统、冻结网络。)

一个耗时 30 秒且不稳定的循环,几乎不比没有循环强;一个耗时 2 秒且结果确定的循环,才算紧凑,是调试的超能力。

非确定性 Bug

目标不是获得干净的复现,而是提高复现率。将触发条件循环执行 100 次、并行运行、增加压力、缩小时间窗口、注入延迟。复现率为 50% 的 Bug 可以调试;1% 则不行,因此要持续提高复现率,直到能够调试为止。

确实无法构建循环时

停止并明确说明。列出你尝试过的方法。向用户请求:(a)对能复现问题的环境的访问权限;(b)脱敏后的捕获工件(HAR 文件、日志转储、核心转储、带时间戳的屏幕录像);或(c)添加临时生产环境检测的许可。没有反馈循环时,不要继续提出假设。

完成标准:一个能稳定触发失败的紧凑循环

当循环足够紧凑且能够触发失败时,Phase 1 就完成了:你可以指出一条命令(脚本路径、测试调用或 curl),并且至少运行过一次(展示调用和输出,注意脱敏),且该命令满足以下条件:

  • 能够触发失败:它会驱动实际的 Bug 代码路径,并断言用户遇到的确切症状,因此遇到这个 Bug 时能失败,修复后能通过。不是“运行时没有报错”就够了;它必须能_捕获这个特定 Bug_。
  • 确定性:每次运行结果都相同(对于不稳定的 Bug,按照上述说明,需固定并达到较高的复现率)。
  • 快速:耗时以秒计,而不是分钟。
  • 可由 Agent 运行:可以无人值守地运行;只有通过 scripts/hitl-loop.template.sh 才需要人工介入。

如果你发现自己在这条命令还不存在时,就开始读代码构建理论,请停下来:直接跳到假设阶段,正是这项技能要避免的问题。 没有能触发失败的命令,就不能进入 Phase 2。

Phase 2:复现并缩小范围

运行循环。观察它在 Bug 出现时失败。

确认:

  • 循环产生的是用户描述的故障模式,而不是碰巧相关的另一种故障。Bug 找错了,修复也会错。
  • 多次运行都能复现故障(对于非确定性 Bug,复现率必须足够高,才能据此调试)。
  • 已捕获确切症状(错误消息、错误输出、耗时过长),以便后续阶段验证修复是否确实解决了问题。
缩小范围

确认循环会失败后,将复现缩减为仍能触发失败的最小场景。每次只删减一项输入、调用方、配置、数据或步骤,并在每次删减后重新运行循环,只保留对故障起关键作用的内容。

这样做的原因:最小复现能在 Phase 3 缩小假设范围(可疑因素更少),并在 Phase 5 成为干净的回归测试。

当剩下的每个元素都起关键作用时,即可完成:移除其中任意一个,循环都会通过。

完成复现并缩小范围之前,不要继续。

Phase 3:提出假设

在测试任何假设之前,先生成 3–5 个按优先级排序的假设。只生成一个假设会让人锚定在第一个看似合理的想法上。

每个假设都必须可证伪:说明它会给出什么预测。

格式:“如果 是原因,那么 <改变 Y> 将使 Bug 消失/<改变 Z> 将使问题恶化。”

如果无法说明预测,这个假设就只是凭感觉:舍弃它或进一步明确。

测试假设之前,先向用户展示排序后的列表。 用户通常掌握领域知识,可以立即重新排序(“我们刚部署了与 #3 相关的改动”),或者知道哪些假设已经被排除。这是成本低、收益大的检查点。不要因此停滞;如果用户暂时不在线,就按自己的排序继续。

Phase 4:加设探针

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

工具优先顺序:

  1. 如果环境支持,优先使用调试器/REPL 检查。一个断点胜过十条日志。
  2. 在能够区分不同假设的边界处添加定向日志。
  3. 绝不要“记录所有内容再用 grep 筛选”。

为每条调试日志添加唯一前缀,例如 [DEBUG-a4f2]。最后只需一次 grep 就能清理干净。未加标签的日志会残留;加了标签的日志则会被清除。

性能分支。 对于性能回退,日志通常不是正确的办法。相反,应先建立基准测量(计时测试框架、performance.now()、性能分析器、查询计划),然后进行二分排查。先测量,再修复。

第 5 阶段:修复 + 回归测试

先编写回归测试,再进行修复,但前提是存在合适的切入点。

合适的切入点是指测试能在调用点复现真实的缺陷模式。如果唯一可用的切入点过于浅显(例如,缺陷需要多个调用方,但测试只涉及单个调用方;或者单元测试无法复现触发缺陷的调用链),那么在那里编写回归测试会造成虚假的信心。

**如果不存在合适的切入点,这本身就是发现的问题。**请记录下来。代码库架构导致无法将该缺陷固定下来。请在下一阶段标记此问题。

如果存在合适的切入点:

  1. 将最小复现转化为该切入点上的失败测试。
  2. 确认测试失败。
  3. 应用修复。
  4. 确认测试通过。
  5. 针对原始(未经最小化的)场景,重新运行第 1 阶段的反馈循环。

第 6 阶段:清理

宣布完成之前,必须:

  • 原始复现不再能复现该问题(重新运行第 1 阶段的反馈循环)
  • 回归测试通过(或已记录不存在合适的切入点)
  • 已移除所有 [DEBUG-...] 插桩(使用 grep 搜索该前缀)
  • 已删除临时原型(或将其移至明确标记为调试用途的位置)
  • 在提交 / PR 消息中说明最终证实的假设,让后续调试人员能够从中吸取经验

评论

支持 Markdown