关于 debug
Debug 是用于调查复杂软件故障的结构化技能,面向开发者和编码 Agent。它要求先建立并缩减可复现的反馈循环,再提出排序后的假设,并指导探查、回归测试与清理。
主要功能
- 01建立可判定失败的反馈循环
- 02复现并缩减故障场景
- 03排列可证伪的假设
- 04通过定向插桩进行探查
- 05添加回归测试并清理
适用场景
- 01诊断症状明确且可复现的故障
- 02调查间歇性失败
- 03追踪性能回退
- 04依据原始场景验证修复
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ dyrits/ skills --skill debug安装后在 Claude Code 中输入/debug确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
apm.ymlagents/openai.yamlassets/hitl-loop.template.sh详细介绍
调试
用于处理难以修复的 bug 的方法。只有在明确说明理由时才跳过阶段。
探索代码库时,如果 AGENTS.md 指向相关内容,请阅读你正在处理的区域中的术语表和架构决策记录,以清楚了解相关模块。报告中提及一个它未提到但可能相关的内容,而不是使用它。
脱敏
此技能要求你展示命令、输出和捕获的材料。先对所有秘密信息进行脱敏:用 <REDACTED> 替换。围绕环境变量构建循环,让凭据留在环境中,而不是出现在展示内容里。捕获的材料可能包含身份验证标头:只引用能体现关键信息的行。
如果脱敏后的输出不足以诊断 bug,请说明这一点并询问用户。
阶段 1:构建反馈循环
这就是本技能的核心。其他一切都是机械性的。如果你有一个针对该 bug 的紧密通过/失败信号(能在_这个_ bug 出现时变为失败),就能找到原因;二分查找、假设检验和插桩都只是利用这个信号。如果没有,再怎么盯着代码看也无济于事。
在这一步投入不成比例的精力。积极尝试。发挥创造力。不要轻言放弃。
构建反馈循环的方法,大致按优先顺序排列
- 失败测试:在任何能触及 bug 的边界上编写测试,包括单元测试、集成测试和端到端测试。
- Curl/HTTP 脚本:针对正在运行的开发服务器运行。
- CLI 调用:使用夹具输入,并将标准输出与已知正常的快照进行比较。
- 无头浏览器脚本:使用 Playwright/Puppeteer 驱动 UI,并断言 DOM/控制台/网络结果。
- **重放捕获的跟踪记录。**将真实的网络请求/负载/事件日志保存到磁盘,再通过隔离的代码路径重放。
- **临时测试框架。**启动系统的最小子集(一个服务、模拟依赖),通过一次函数调用触发 bug 所在的代码路径。
- **属性/模糊测试循环。**如果 bug 表现为“有时输出错误”,就运行 1000 个随机输入并查找故障模式。
- **二分查找测试框架。**如果 bug 出现在两个已知状态之间(提交、数据集、版本),就自动执行“在状态 X 启动、检查、重复”,以便运行
git bisect run。 - **差异循环。**使用相同输入分别运行旧版本与新版本(或两种配置),然后比较输出。
- **HITL bash 脚本。**最后的手段。如果必须由人点击,就使用
assets/hitl-loop.template.sh的副本引导_他们_,让循环仍然有条理:填写步骤,请用户在自己的终端中运行,并读取他们粘贴回来的KEY=VALUE行。
构建正确的反馈循环,bug 就解决了 90%。
收紧循环
把循环当作产品来打磨。构建出_一个_循环后,继续收紧它:
- 我能让它更快吗?(缓存设置、跳过无关初始化、缩小测试范围。)
- 我能让信号更明确吗?(针对具体症状断言,而不是只检查“没有崩溃”。)
- 我能让结果更确定吗?(固定时间、设定随机数种子、隔离文件系统、冻结网络。)
30 秒的间歇性失败循环几乎不比没有循环强;2 秒且结果确定的循环才足够紧凑,是调试的超能力。
非确定性 bug
目标不是获得干净的复现,而是提高复现率。将触发条件循环 100 次、并行运行、增加压力、缩小时间窗口、注入休眠。复现概率为 50% 的间歇性 bug 可以调试;1% 的不行,所以要持续提高复现率,直到可以调试为止。
确实无法构建循环时
停止并明确说明。列出你尝试过的操作。向用户索取:(a) 对复现问题所需环境的访问权限,(b) 脱敏后的捕获材料(HAR 文件、日志转储、核心转储、带时间戳的屏幕录制),或 (c) 添加临时生产环境插桩的许可。不要在没有循环的情况下继续提出假设。
完成标准:一个能稳定触发失败的紧凑循环
当循环既紧凑又能触发失败时,阶段 1 就完成了:你可以说出一条命令(脚本路径、测试调用或 curl),并且至少已经运行过一次(展示调用和输出,并做脱敏),而且这条命令:
- 能触发失败:它会走实际的错误代码路径,并断言用户遇到的确切症状,因此针对这个错误时能够失败,修复后能够通过。不能只是“运行时没有报错”;它必须能_捕获这个特定错误_。
- 确定性:每次运行的判定结果相同(对于不稳定错误,按上述说明,复现率必须足够高且固定)。
- 快速:几秒,而不是几分钟。
- 可由 Agent 运行:可以无人值守地运行;只有通过
assets/hitl-loop.template.sh才需要人工参与。
如果你发现自己正在阅读代码、构建理论,而这条命令还不存在,**请停下来:直接跳到假设正是这项技能要防止的问题。**没有能触发失败的命令,就不要进入阶段 2。
阶段 2:复现 + 最小化
运行循环。观察它在错误出现时触发失败。
确认:
- 循环产生的是用户描述的故障模式,而不是碰巧发生在附近的另一种故障。错误的问题就会导致错误的修复。
- 多次运行都能复现故障(对于非确定性错误,则复现率要足够高,以便据此调试)。
- 已记录确切症状(错误消息、错误输出、耗时过长),以便后续阶段验证修复确实解决了问题。
最小化
循环触发失败后,将复现缩减为仍能触发失败的最小场景。一次只删减一项输入、调用方、配置、数据或步骤,每次删减后都重新运行循环,并且只保留导致故障不可或缺的部分。
为什么要做这件事:最小复现可以缩小阶段 3 的假设空间(可疑因素更少),也能成为阶段 5 中简洁的回归测试。
当剩余的每个元素都不可或缺时,就算完成:移除其中任何一个元素,循环都会通过。
复现并且完成最小化之前,不要继续。
阶段 3:提出假设
在测试任何假设之前,先生成 3–5 个按优先级排序的假设。只生成一个假设会让你锚定在第一个看似合理的想法上。
每个假设都必须可证伪:说明它会产生什么预测。
格式:“如果 是原因,那么 <更改 Y> 会让错误消失 / <更改 Z> 会让错误加重。”
如果无法说明预测,这个假设就只是凭感觉:要么舍弃,要么进一步明确。
**测试之前,先向用户展示排序后的列表。**他们通常掌握领域知识,能立即调整优先级(“我们刚刚部署了对 #3 的更改”),也可能知道哪些假设已经排除。这是成本很低却能节省大量时间的检查点。不要因此停滞;如果用户暂时不在线,就按你的排序继续。
阶段 4:添加检测
每个探查都必须对应阶段 3 中的一个具体预测。一次只更改一个变量。
工具优先级:
- 如果环境支持,优先使用调试器 / REPL 检查。一个断点胜过十条日志。
- 在能区分不同假设的边界处添加有针对性的日志。
- 绝不要“记录所有内容再 grep”。
为每条调试日志加上唯一前缀,例如 [DEBUG-a4f2]。最后清理时只需执行一次 grep。未加标记的日志会残留下来;加了标记的日志则会被清除。
**性能分支。**对于性能回归,日志通常不是正确的工具。应先建立基准测量(计时工具、performance.now()、性能分析器、查询计划),然后再做二分查找。先测量,再修复。
第 5 阶段:修复 + 回归测试
只有在存在合适的切入点时,才在修复前编写回归测试。
复现只能说明存在故障,并不意味着可以更改已达成一致的行为。当症状、规范和代码对预期行为的理解不一致时,应将这一冲突告知用户,并保留已观察到的失败和通过证据,而不是削弱要求来让流程通过。
合适的切入点是指测试能在调用点复现实际发生的错误模式。如果唯一可用的切入点过于浅层(例如,问题需要多个调用方,但测试只涉及单个调用方;或单元测试无法复现触发问题的调用链),那么在此处编写回归测试只会带来虚假的信心。
如果不存在合适的切入点,这本身就是一项发现。 记下来。代码库架构阻碍了对该问题的可靠防护。请在下一阶段标记此事。
如果存在合适的切入点:
- 将最小化复现转化为该切入点处的失败测试。
- 观察测试失败。如果你通过修改代码或测试夹具强行制造失败,请与原始副本执行
diff,确认修改确实生效后再采信结果。 - 应用修复。
- 观察测试通过。
- 针对原始(未最小化)场景,重新运行第 1 阶段的反馈循环。
第 6 阶段:清理
宣布完成前,必须完成以下事项:
- 原始复现不再复现问题(重新运行第 1 阶段的反馈循环)
- 回归测试通过(或已记录不存在合适的切入点)
- 移除所有
[DEBUG-...]埋点(使用grep搜索该前缀) - 删除临时原型(或将其移至明确标记的调试目录)
- 在提交 / PR 消息中说明最终证实正确的假设,让后续调试人员有所借鉴