关于 diagnosing-bugs
面向编码 Agent 的疑难调试流程,要求先建立快速、确定的反馈循环,再提出假设或深入检查代码。它还强调缩小复现范围、验证修复,并在证据中遮蔽机密。
主要功能
- 01构建针对性的通过/失败反馈循环
- 02复现并缩小故障范围
- 03排列可证伪的假设
- 04有选择地检查边界
- 05添加回归测试并验证修复
- 06遮蔽证据中的机密
适用场景
- 01调查间歇性故障
- 02诊断涉及多个调用方的错误
- 03创建最小回归测试
- 04请求缺失的复现证据
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ leandrocfe/ 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 失败的信号——你就能找到原因;二分查找、假设检验和插桩都只是利用这个信号的手段。如果没有这个信号,再怎么查看代码也无济于事。
在这里投入远超其他阶段的精力。积极尝试。大胆创新。不要轻易放弃。
构建反馈循环的方法——大致按以下顺序尝试
- 失败测试:在能触及 Bug 的接缝处编写测试——单元测试、集成测试、端到端测试。
- Curl/HTTP 脚本:针对正在运行的开发服务。
- CLI 调用:使用输入样例,将 stdout 与已知正常的快照进行差异比较。
- 无头浏览器脚本(Playwright/Puppeteer):驱动 UI,并断言 DOM/控制台/网络状态。
- 重放捕获的 trace。 将真实网络请求/载荷/事件日志保存到磁盘;通过隔离环境中的代码路径重放。
- 一次性测试框架。 启动系统的最小子集(一个服务、模拟依赖),通过单次函数调用测试 Bug 所在的代码路径。
- 属性/模糊测试循环。 如果 Bug 表现为“有时输出错误”,就运行 1000 个随机输入并查找失败模式。
- 二分查找测试框架。 如果 Bug 出现在两个已知状态之间(提交、数据集、版本),就自动执行“在状态 X 启动、检查、重复”,以便使用
git bisect run。 - 差异循环。 对旧版本与新版本(或两种配置)运行相同输入,并比较输出。
- 人工参与的 bash 脚本。 最后的手段。如果需要人工点击,就用
scripts/hitl-loop.template.sh驱动_人工_,使循环仍然有条理。捕获的输出会返回给你。
构建好正确的反馈循环,Bug 就算解决了 90%。
收紧循环
把循环当作产品来打磨。构建好_一个_循环后,继续收紧:
- 能不能让它更快?(缓存设置、跳过无关的初始化、缩小测试范围。)
- 能不能让信号更清晰?(断言具体症状,而不只是“没有崩溃”。)
- 能不能让结果更确定?(固定时间、设置随机数种子、隔离文件系统、冻结网络。)
一个运行 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让人工参与循环。
如果你发现自己在这条命令尚未存在之前就开始阅读代码、构建理论,**请停下——直接跳到假设阶段正是这项技能要防止的失误。**没有能够触发失败的命令,就不要进入第 2 阶段。
第 2 阶段——复现 + 最小化
运行循环。观察它失败——bug 出现了。
确认:
- 循环产生的是用户描述的故障模式,而不是附近发生的其他故障。bug 错了,修复也会错。
- 故障可在多次运行中复现(对于非确定性 bug,则须有足够高的复现率,以便调试)。
- 你已捕获确切症状(错误消息、错误输出、耗时过长),以便后续阶段验证修复是否确实解决了问题。
最小化
一旦复现失败,就将复现缩减为仍能触发失败的最小场景。每次只删减一个输入、调用方、配置、数据或步骤;每次删减后都重新运行循环——只保留对故障至关重要的部分。
这样做的价值:最小复现能缩小第 3 阶段的假设范围(可疑的活动部件更少),并在第 5 阶段成为干净的回归测试。
当剩下的每个元素都不可或缺时,即可完成——移除任何一个元素都会让循环通过。
在复现并且完成最小化之前,不要继续。
第 3 阶段——提出假设
在测试任何假设之前,先生成3–5 个按可能性排序的假设。只生成一个假设会让人锚定在第一个看似合理的想法上。
每个假设都必须可证伪:说明它会带来什么预测。
格式:“如果 是原因,那么 <更改 Y> 会让 bug 消失 / <更改 Z> 会让情况恶化。”
如果你无法说明预测,这个假设就只是凭感觉——请舍弃或细化。
**先向用户展示排序后的列表,再进行测试。**他们通常掌握能立即调整排序的领域知识(“我们刚刚部署了 #3 的变更”),也可能知道哪些假设已被排除。这是低成本检查点,却能节省大量时间。不要因此停滞——如果用户暂时不在线,就按你的排序继续。
第 4 阶段——添加探测
每个探测都必须对应第 3 阶段中的一个具体预测。一次只更改一个变量。
工具优先级:
- 调试器 / REPL 检查(如果环境支持)。一个断点胜过十条日志。
- 在能区分不同假设的边界处添加定向日志。
- 绝不要“把所有东西都记下来再 grep”。
给每条调试日志加上唯一前缀,例如 [DEBUG-a4f2]。最后清理时只需运行一次 grep。未加标记的日志会残留下来;加了标记的日志则会被清除。
**性能问题分支。**对于性能回归,日志通常不是正确工具。应改为:先建立基准测量(计时测试框架、performance.now()、性能分析器、查询计划),然后再进行二分定位。先测量,再修复。
阶段 5 — 修复 + 回归测试
修复前先编写回归测试——但前提是存在合适的接缝。
合适的接缝是指测试能在调用点按照 bug 实际发生的模式进行验证。如果唯一可用的接缝过于浅层(例如,bug 需要多个调用方,但测试只覆盖单个调用方;或者单元测试无法复现触发 bug 的调用链),那么在此处编写回归测试会带来虚假的信心。
**如果不存在合适的接缝,这本身就是发现。**请记录下来。代码库的架构妨碍了对 bug 的锁定。将此问题标记到下一阶段。
如果存在合适的接缝:
- 将最小化复现转化为该接缝处的失败测试。
- 确认测试失败。
- 应用修复。
- 确认测试通过。
- 针对原始(未最小化)场景重新运行阶段 1 的反馈循环。
阶段 6 — 清理 + 事后复盘
在宣布完成之前,必须完成以下事项:
- 原始复现不再能复现问题(重新运行阶段 1 的反馈循环)
- 回归测试通过(或已记录不存在合适的接缝)
- 移除所有
[DEBUG-...]调试代码(对该前缀执行grep) - 删除一次性原型(或将其移至明确标记的调试目录)
- 在提交消息 / PR 中说明已证实的假设——让下一位调试人员有所借鉴
然后请思考:怎样才能避免这个 bug?如果答案涉及架构变更(缺乏合适的测试接缝、调用方纠缠在一起、存在隐藏的耦合),请带上详细信息转到 skill /improve-codebase-architecture。请在修复完成之后再提出建议,而不是之前——现在你掌握的信息比刚开始时更多。