关于 systematic-debugging
Systematic Debugging 是一项指导 Agent 在修改代码前调查技术故障的技能。其四阶段流程强调可复现的测试循环、基于证据的根因分析、假设验证和修复确认。
主要功能
- 01修复前先调查根因
- 02建立快速且可复现的故障循环
- 03追踪代码变更、数据流和组件边界
- 04逐一验证排序后的假设
- 05添加回归测试并确认修复
适用场景
- 01调查失败的测试或生产环境 Bug
- 02复现间歇性行为以便分析
- 03追踪跨系统组件的问题
- 04验证修复方案且避免无关改动
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ nousresearch/ hermes-agent --skill systematic-debugging安装后在 Claude Code 中输入/systematic-debugging确认已加载。
详细介绍
系统化调试
概述
随机修复会浪费时间,还会引入新 Bug。快速打补丁会掩盖根本问题。
核心原则: 尝试修复之前,务必找到根本原因。只修复症状等于失败。
不遵守流程的字面要求,就是违背调试的精神。
铁律
NO FIXES WITHOUT ROOT CAUSE INVESTIGATION FIRST
如果尚未完成阶段 1,就不能提出修复方案。
反馈循环规则
反馈循环就是调试工作。在阅读代码并构建理论之前,先创建或找到一条紧密的命令:它能在用户遇到的确切症状出现时失败,并在 Bug 修复后通过。紧密的循环运行快速、结果确定、可由 Agent 执行,并且足够具体,能捕获这个 Bug,而不只是检查「程序不会崩溃」。
难以建立干净的复现时,就投入不成比例的精力来构建循环。没有能触发失败的循环却靠猜测,正是这项技能旨在避免的失败模式。
适用场景
任何技术问题都应使用:
- 测试失败
- 生产环境中的 Bug
- 意外行为
- 性能问题
- 构建失败
- 集成问题
以下情况尤其要使用:
- 时间紧迫时(紧急情况容易让人忍不住猜测)
- 「只需快速修复一下」看起来显而易见时
- 已经尝试过多种修复方案时
- 之前的修复无效时
- 尚未完全理解问题时
不要在以下情况跳过:
- 问题看起来很简单时(简单的 Bug 也有根本原因)
- 赶时间时(匆忙行事必然导致返工)
- 有人要求立即修复时(系统化调试比反复折腾更快)
四个阶段
必须完成每个阶段后才能进入下一阶段。
阶段 1:根本原因调查
尝试任何修复之前:
1. 仔细阅读错误信息
- 不要略过错误或警告
- 它们通常包含确切的解决方案
- 完整阅读堆栈跟踪
- 记下行号、文件路径和错误代码
操作: 对相关源文件使用 read_file。使用 search_files 在代码库中查找错误字符串。
2. 建立紧密的反馈循环
- 能否用一条命令触发用户遇到的确切症状?
- 这条命令是否会因这个 Bug 失败,并且只有 Bug 修复后才会通过?
- 运行速度是否足够快,能反复执行?
- 结果是否确定?对于不稳定的 Bug,能否提高复现率以便调试?
- 如果无法复现 → 收集更多数据,不要猜测。
构建循环的方法——大致按以下顺序尝试:
- 在能够触达 Bug 的边界处编写失败测试:单元测试、集成测试或端到端测试。
- 使用 HTTP 脚本 / curl 请求正在运行的开发服务。
- 使用测试输入调用 CLI,并将 stdout/stderr 与预期输出进行差异比较。
- 使用无头浏览器脚本(Playwright/Puppeteer)断言 DOM、控制台或网络状态。
- 重放捕获的跟踪记录:HAR、请求载荷、事件日志、队列消息或 webhook 请求体。
- 编写一次性测试框架,启动系统中最小且有用的部分,并调用失败路径。
- 如果 Bug 是在广泛输入空间内间歇性地产生错误输出,则构建属性测试 / 模糊测试循环。
- 如果 Bug 出现在两个已知状态之间,则构建适用于
git bisect run的二分查找测试框架。 - 构建差异比较循环,对比旧版与新版、两种配置、两家提供商或两组数据集。
- 仅作为最后手段使用人机协作脚本:将人工操作步骤编写成脚本并捕获结果,使循环保持结构化。
建立循环后,进一步收紧循环:
- 提升速度:缓存初始化、缩小范围、跳过无关的初始化步骤。
- 提高信号清晰度:断言确切症状,而不是泛泛地检查是否成功。
- 提高确定性:固定时间、设置随机种子、隔离文件系统、冻结网络。
对于非确定性 Bug,眼下的目标是提高复现率,而非追求完美。将触发流程运行 100 次、并行执行、增加压力、缩短时间窗口或注入休眠。复现率为 50% 的不稳定问题可以调试;复现率为 1% 的通常不行。
操作: 使用 terminal 工具运行紧密的反馈循环:
# Run a specific failing test
pytest tests/test_module.py::test_name -v
# Or run a scripted repro
python scripts/repro_bug.py
# Or run a high-repetition flaky repro
for i in {1..100}; do pytest tests/test_flake.py::test_name -q || break; done
3. 检查近期变更
- 有哪些变更可能导致这个问题?
- Git 差异、近期提交
- 新增依赖、配置变更
操作:
# Recent commits
git log --oneline -10
# Uncommitted changes
git diff
# Changes in specific file
git log -p --follow src/problematic_file.py | head -100
4. 在多组件系统中收集证据
当系统包含多个组件时(API → 服务 → 数据库、CI → 构建 → 部署):
提出修复方案之前,先添加诊断埋点:
对于每个组件边界:
- 记录进入组件的数据
- 记录离开组件的数据
- 验证环境和配置是否传递
- 检查每一层的状态
运行一次,收集证据以确定问题出在哪里。 然后分析证据,找出故障组件。 然后调查该组件。
5. 追踪数据流
当错误发生在调用栈深处时:
- 错误值从哪里来?
- 是什么调用了这个函数,并传入错误值?
- 持续向上追踪,直到找到源头
- 在源头修复,而不是处理症状
操作: 使用 search_files 追踪引用:
# Find where the function is called
search_files("function_name(", path="src/", file_glob="*.py")
# Find where the variable is set
search_files("variable_name\\s*=", path="src/", file_glob="*.py")
阶段 1 完成检查清单
- 已完整阅读并理解错误消息
- 已准备好一个紧凑的循环命令,并至少运行过一次
- 该循环能够准确复现失败:它断言用户遇到的确切症状,而不是相近的问题
- 该循环具有确定性,或者不稳定问题的复现率足够高,可以进行调试
- 已识别并审查近期变更
- 已收集证据(日志、状态、数据流)
- 已将问题定位到特定组件/代码
- 能够陈述并验证根因假设
停止: 在理解问题发生的原因之前,不要进入阶段 2。
阶段 2:模式分析
先找到模式,再进行修复:
0. 精简复现步骤
循环能够复现失败后,将复现步骤缩减到仍然失败的最小场景。每次只删除一项输入、调用方、配置、数据或步骤,并在每次删减后重新运行循环。只保留导致失败所必需的内容。
当删除任何剩余元素都会让循环转为成功时,就算完成。精简复现可以缩小假设范围,而且往往能成为最清晰的回归测试。
1. 查找可正常工作的示例
- 在同一代码库中找到类似的可正常工作代码
- 哪些类似的功能正常,而哪些功能有问题?
操作: 使用 search_files 查找可比较的模式:
search_files("similar_pattern", path="src/", file_glob="*.py")
2. 对照参考实现
- 如果要实现某种模式,请完整阅读参考实现
- 不要略读——逐行阅读
- 完全理解该模式后再应用
3. 找出差异
- 正常代码和有问题的代码之间有什么不同?
- 列出所有差异,无论多么细微
- 不要认定“这不可能有影响”
4. 理解依赖关系
- 这需要哪些其他组件?
- 需要哪些设置、配置和环境?
- 它有哪些假设?
阶段 3:假设与测试
采用科学方法:
1. 提出可证伪假设并按可能性排序
- 在测试任何一个假设之前,先提出 3–5 个合理的假设。
- 按可能性和证伪成本从低到高排序。
- 说明每个假设的预测:“如果 X 是原因,那么更改或观察 Y 应该会导致 Z。”
- 丢弃或完善无法给出可测试预测的假设。
如果用户在场,测试前先展示排序后的列表。他们可能掌握相关领域知识,能立即调整排序。如果用户暂时不在,按你的排序继续。
2. 尽量精简地测试
- 用尽可能小的探查来测试排序最高的假设。
- 每次只更改一个变量。
- 不要一次修复多个问题。
- 如果可以,优先使用调试器/REPL 检查;一个断点胜过十条日志。
- 如果添加日志,用唯一前缀标记每条临时日志行,例如
[DEBUG-a4f2],这样只需搜索一次就能清理干净。
3. 继续之前先验证
- 是否有效?→ 阶段 4
- 无效?→ 提出新假设
- 不要在此基础上继续叠加修复
4. 遇到不确定时
- 说“我不理解 X”
- 不要假装自己明白
- 向用户寻求帮助
- 进一步调查
第 4 阶段:实现
修复根本原因,而不是表面症状:
1. 创建失败测试用例
- 尽可能简单地复现问题
- 尽可能编写自动化测试
- 修复前必须先创建测试
- 使用
test-driven-development技能
2. 实施单一修复
- 解决已确认的根本原因
- 一次只做一项更改
- 不要顺手改进其他内容
- 不要捆绑重构
3. 验证修复
# Run the specific regression test
pytest tests/test_module.py::test_regression -v
# Run full suite — no regressions
pytest tests/ -q
4. 如果修复无效——三次规则
- 停止。
- 统计:你已经尝试了多少次修复?
- 如果少于 3 次:返回第 1 阶段,根据新信息重新分析
- 如果达到或超过 3 次:停止并质疑架构(见下方第 5 步)
- 未经架构讨论,不要尝试第 4 次修复
5. 如果 3 次或更多修复均失败:质疑架构
表明存在架构问题的模式:
- 每次修复都会在不同位置发现新的共享状态或耦合
- 修复需要“大规模重构”才能实施
- 每次修复都会在其他地方引发新的症状
停止并质疑基本假设:
- 这种模式从根本上是否合理?
- 我们是否只是“凭惯性坚持下去”?
- 是否应该重构架构,而不是继续修复症状?
尝试更多修复之前,先与用户讨论。
这不是假设失效,而是架构错误。
危险信号——停止并遵循流程
如果你发现自己在想:
- “先快速修复,以后再调查”
- “只要试着改一下 X,看看是否有效”
- “一次做多项更改,然后运行测试”
- “跳过测试,我会手动验证”
- “问题可能出在 X,我来修复”
- “我还没完全弄明白,但这可能有效”
- “模式要求做 X,但我会用另一种方式调整”
- “以下是主要问题:[未经调查就列出修复方案]”
- 尚未追踪数据流就提出解决方案
- “再试一次修复”(已经尝试过 2 次或更多时)
- 每次修复都会在不同位置发现新问题
所有这些都意味着:停止。返回第 1 阶段。
**如果 3 次或更多修复均失败:**质疑架构(第 4 阶段第 5 步)。
常见的自我辩解
| 借口 | 现实 |
|---|---|
| “问题很简单,不需要流程” | 简单问题也有根本原因。流程能快速处理简单 Bug。 |
| “这是紧急情况,没时间走流程” | 系统化调试比反复猜测和试错更快。 |
| “先试试这个,再调查” | 第一次修复会定下模式。从一开始就做好。 |
| “确认修复有效后再写测试” | 未经测试的修复难以持久。先写测试才能验证修复。 |
| “一次做多个修复能节省时间” | 无法确定哪项修复有效,还会引发新 Bug。 |
| “参考资料太长,我会自行调整模式” | 只理解一部分必然会引发 Bug。请完整阅读。 |
| “我看出问题了,我来修复” | 看到症状 ≠ 理解根本原因。 |
| “再试一次修复”(失败 2 次或更多后) | 失败 3 次或更多 = 架构问题。质疑模式,不要再次修复。 |
快速参考
| 阶段 | 关键活动 | 成功标准 |
|---|---|---|
| 1. 根本原因 | 阅读错误信息、复现问题、检查更改、收集证据、追踪数据流 | 理解是什么问题以及为什么 |
| 2. 模式 | 查找可用示例、进行比较、找出差异 | 知道差异所在 |
| 3. 假设 | 提出理论、进行最小化测试、一次只改变一个变量 | 得到确认或提出新假设 |
| 4. 实现 | 创建回归测试、修复根本原因、验证 | Bug 已解决,所有测试通过 |
Hermes Agent 集成
调查工具
在第 1 阶段使用以下 Hermes 工具:
search_files— 查找错误字符串、追踪函数调用、定位模式read_file— 阅读带行号的源代码,以便精确分析terminal— 运行测试、检查 Git 历史、复现 Bugweb_search/web_extract— 查询错误信息和库文档
使用 delegate_task
对于涉及多个组件的复杂调试,派遣调查子 Agent:
delegate_task(
goal="Investigate why [specific test/behavior] fails",
context="""
Follow systematic-debugging skill:
1. Read the error message carefully
2. Reproduce the issue
3. Trace the data flow to find root cause
4. Report findings — do NOT fix yet
Error: [paste full error]
File: [path to failing code]
Test command: [exact command]
""",
toolsets=['terminal', 'file']
)
使用测试驱动开发
修复错误时:
- 编写一个能复现错误的测试(RED)
- 系统地调试,找到根本原因
- 修复根本原因(GREEN)
- 测试验证修复有效,并防止回归
实际影响
根据调试过程的经验:
- 系统化方法:15–30 分钟即可修复
- 随机修复方法:折腾 2–3 小时
- 首次修复成功率:95% 对比 40%
- 引入的新错误:几乎为零,对比经常发生
不走捷径,不靠猜测。系统化方法始终更胜一筹。