关于 diagnosing-bugs
面向编码 Agent 的系统化调试技能,强调先建立快速、确定且能捕获故障的反馈循环,再提出假设。流程涵盖复现、诊断、回归测试与清理。
主要功能
- 01建立可捕获故障的反馈循环
- 02复现并缩小故障范围
- 03排列可证伪的假设
- 04针对性添加诊断探针
- 05编写回归测试并清理
适用场景
- 01调查间歇性故障
- 02追踪不同版本间的回归
- 03诊断跨系统边界的错误
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ hu3rror/ my-skills --skill diagnosing-bugs安装后在 Claude Code 中输入/diagnosing-bugs确认已加载。
附带文件agents/openai.yamlscripts/hitl-loop.template.sh
详细介绍
诊断 Bug
一套用于处理棘手 Bug 的方法。只有明确说明理由时,才跳过阶段。
探索代码库时,请阅读 GLOSSARY.md(如果存在),以便清楚了解相关模块,并检查你正在处理区域的 ADR。
如果 Bug 的线索涉及过去的 pi 会话(问题发生在更早的一次运行中,或某项行为在两次运行之间发生了变化),请使用 pi-session-search 技能搜索,而不要临时使用 grep/python:它支持 UTF-8,了解 JSONL 结构,并会限制输出长度。
脱敏
这项技能要求你展示命令、输出和捕获的工件。请先对所有机密信息进行脱敏:用 <REDACTED> 替代。通过环境变量构建循环,让凭据留在环境中,而不是出现在你展示的内容里。捕获的工件包含身份验证头:只引用能体现关键信息的行。
如果脱敏后的输出不足以诊断 Bug,请说明这一点并询问用户。
阶段 1:构建反馈循环
这就是这项技能的核心。其他一切都只是机械操作。如果你有一个针对该 Bug 的快速通过/失败信号(能在_这个_ Bug 出现时变红),就能找到原因;二分查找、假设检验和插桩都只是利用它的手段。如果没有这样的信号,再怎么盯着代码看也无济于事。
在这里投入远超常规的精力。积极尝试。发挥创造力。不要轻言放弃。
构建反馈循环的方法,按大致优先级排序
- 在能触及 Bug 的位置编写失败测试:单元测试、集成测试、端到端测试均可。
- 对运行中的开发服务器使用 Curl/HTTP 脚本。
- 使用夹具输入调用 CLI,并将标准输出与已知的正确快照进行比较。
- 使用无头浏览器脚本(Playwright/Puppeteer)驱动界面,并断言 DOM/控制台/网络状态。
- **重放捕获的跟踪记录。**将真实网络请求/载荷/事件日志保存到磁盘,再通过隔离环境中的代码路径重放。
- **临时测试框架。**启动系统的最小子集(一个服务、模拟的依赖),通过一次函数调用走通 Bug 对应的代码路径。
- **属性/模糊测试循环。**如果 Bug 表现为“输出有时不正确”,就运行 1000 个随机输入,并查找故障模式。
- **二分查找测试框架。**如果 Bug 出现在两个已知状态(提交、数据集、版本)之间,就自动执行“在状态 X 启动,检查,重复”,以便运行
git bisect run。 - **差分循环。**对同一输入分别运行旧版本和新版本(或两种配置),并比较输出。
- **人工介入的 Bash 脚本。**最后的手段。如果必须由人来点击,就使用
scripts/hitl-loop.template.sh驱动_他们_,让循环仍然有条理。捕获的输出会反馈给你。在 Windows 上,请使用 Git Bash 或 WSL 运行——这是一个 Bash 脚本,PowerShell 无法直接运行(在 Git Bash 中运行bash scripts/hitl-loop.template.sh,或在 PowerShell 中运行wsl bash "$(wsl wslpath -a 'C:/path/to/hitl-loop.template.sh')")。
构建合适的反馈循环,Bug 就解决了 90%。
收紧循环
把循环当作产品。构建出_一个_循环后,继续收紧它:
- 能否让它更快?(缓存初始化、跳过无关的初始化、缩小测试范围。)
- 能否让信号更明确?(断言具体症状,而不是仅仅检查“没有崩溃”。)
- 能否让结果更确定?(固定时间、为随机数生成器设定种子、隔离文件系统、冻结网络。)
一个运行 30 秒且时好时坏的循环,几乎不比没有循环强;一个运行 2 秒且结果确定的循环才够紧凑,是调试的超能力。
非确定性 Bug
目标不是得到干净的复现,而是提高复现率。将触发条件循环 100×,并行运行、增加压力、缩小时间窗口、插入休眠。复现率为 50% 的间歇性 Bug 仍可调试;1% 的则不行,所以要持续提高复现率,直到它变得可调试。
确实无法构建循环时
明确停下来说明。列出你尝试过的内容。请用户提供以下任一项:(a)可复现问题的环境访问权限;(b)经过脱敏的捕获产物(HAR 文件、日志转储、核心转储、带时间戳的屏幕录像);或(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> 会让 Bug 加重。”
如果无法说明预测,这个假设就只是凭感觉:要么舍弃,要么进一步明确。
在测试之前,先向用户展示按可能性排序的列表。 用户往往掌握相关领域知识,能立即重新排序(“我们刚部署了 #3 相关的更改”),或者知道哪些假设已经排除。这是个成本低、收益大的检查点。不要因此停滞不前;如果用户暂时不在,就按你的排序继续。
阶段 4:添加检测
每个探查步骤都必须对应阶段 3 中的一条具体预测。一次只更改一个变量。
工具选择优先级:
- 调试器/REPL 检查(如果环境支持)。一个断点胜过十条日志。
- 在能区分不同假设的边界处添加有针对性的日志。
- 绝不要“把所有内容都记下来再 grep”。
给每条调试日志加上唯一前缀,例如 [DEBUG-a4f2]。最后清理时只需 grep 一次。未加标记的日志会遗留下来;加了标记的日志则可以清理。
性能分支。 对于性能回归,日志通常不是正确做法。应先建立基线测量(计时工具、performance.now()、性能分析器、查询计划),然后进行二分排查。先测量,再修复。
阶段 5:修复 + 回归测试
仅当存在合适的 seam 时,才在修复之前编写回归测试。
合适的 seam 是指测试能够在调用点上复现真实的 bug 模式。如果唯一可用的 seam 过于浅层(例如只有单一调用方的测试,而 bug 需要多个调用方才能触发;或者单元测试无法复现触发 bug 的调用链),那么在该处编写回归测试会带来虚假的信心。
如果不存在合适的 seam,这本身就是一项发现。 请记录下来。这说明代码架构阻碍了对该 bug 的固化验证。将其标记,交由下一阶段处理。
如果存在合适的 seam:
- 将最小化复现转换为该 seam 处的失败测试。
- 确认测试确实失败。如果你是通过修改代码或测试夹具来强制得到红色结果,请与未修改的原始副本执行
diff,以证明修改已生效,之后再信任该结果。 - 应用修复。
- 确认测试通过。
- 针对原始(未最小化)场景,重新运行阶段 1 的反馈循环。
阶段 6:清理
宣告完成前必须满足以下条件:
- 原始复现不再出现(重新运行阶段 1 的循环)
- 回归测试通过(或已记录缺少 seam 的情况)
- 所有
[DEBUG-...]调试代码已移除(用grep搜索该前缀) - 一次性原型已删除(或已移至明确标注的调试目录)
- 在提交信息或 PR 描述中写明最终被证实正确的假设,以便后续调试者从中学习