| name | debug |
| description | 当用户报告 bug、报错、崩溃、异常输出、行为不符合预期,或测试/构建失败,询问为什么失败、
哪一行出错,或要求调试、debug、定位、排查、调查、修复问题时使用。
|
| metadata | {"openclaw":{"emoji":"🐞"}} |
debug — 调试与问题定位技能
执行前置
遵循当前目录 AGENTS.md「技能执行公共契约」;仅按需读取技能正文与 reference。
核心原则
- 先复现,后定位:不能稳定复现的问题无法验证修复是否有效;复现路径必须最小化。
- 证据驱动:以报错文本、退出码、诊断输出和 Git 变更为证据,禁止凭印象猜测。
- 二分收敛:用 delta-debug 思想缩小嫌疑范围(注释代码段、加打印、git bisect)。
- 不变量校验:以守恒律/不变量与边界情形(空输入、0、越界、退化情形)校验结论;
"行为无报错但结果错"的常见根因是不变量被破坏。
- 最小修复:只改导致问题的行,遵循代码库既有约定;修复与重构分离。
- 验证闭环:修复后用原复现路径回归,再跑仓库的 lint/typecheck/test
(纯 shell 仓库至少
bash -n)。
- 循环尝试直至成功:一次修复失败不停止——回到定位阶段,基于新证据重新分析、
优化方案后再执行,循环迭代直至成功或用户终止;每次迭代都要更新终端摘要。
铁律:无根因调查,禁止修复
NO FIXES WITHOUT ROOT CAUSE INVESTIGATION FIRST
- 未完成根因调查(复现→证据→定位)之前,不得提出任何修复方案;症状修复等于失败。
- 连续 3 次修复失败时停止逐症状修复,质疑架构本身:
若每修一处就暴露新的耦合问题,说明问题在架构/模式层面而非单点代码,
与用户讨论是否重构,而非继续第 4 次盲修。
- 这个原则是调试的精神内核:过程即立场,任何一步跳过都意味着退回到猜测式调试。
红旗清单(出现即回到根因调查)
调试过程中一旦出现以下想法或话语,立即停下,回到「Step 3 缩小范围」:
| 红旗 | 含义 |
|---|
| "先快速修一下,以后再调查" | 快速修复=症状修复,第一次修复就决定后续方向 |
| "试试改 X 看行不行" | 猜测,不是假设;假设必须说明"因为 Y 所以 X" |
| "同时改多个地方,一起跑测试" | 无法隔离哪个改动生效,且会引入新 bug |
| "跳过测试,手动验证就行" | 未测的修复不成立;先写失败用例再修 |
| "大概是 X 的问题,改掉它" | 看到症状≠理解根因 |
| "不太懂但这样可能行" | 承认"我不懂 X",去查证,不装懂 |
| "再试一次就好"(已失败 2+ 次) | 3 次失败=架构问题,不是运气问题 |
| 每次修复都在不同位置暴露新问题 | 架构耦合的证据,停止修复,质疑设计 |
触发时机
- 用户报告 bug:"报错"、"出错"、"崩溃"、"异常"、"行为不对"、"为什么失败"、"定位问题"
- 用户明确要求调试:"debug"、"调试"、"排查"、"调查"、"修 bug"、"哪一行出错"
- 测试/脚本失败:"test 挂了"、"运行报错"、"exit code"、"command not found"、"Permission denied"
- 行为不符合预期但无报错:输出错误、路径错误、环境变量未生效
- 与其他技能配合:性能问题 → optim;查看引入问题的变更 → diff;修复后打标 → tag;
修复后验证 → test;初始化相关 → init;
多个独立失败(互不相关的文件/子系统)→ dispatch 并行排查(相关失败不并行,
修一个可能带好另一个时先合并调查)
工作流程
Step 1. 理解问题(先问对问题)
收集并确认以下信息,缺项时先向用户提问(所有问题在第一次交互一次性全部提出,用户一次回答,不逐次追问),不臆断:
| 信息项 | 说明 |
|---|
| 期望行为 | 用户期望发生什么 |
| 实际行为 | 实际发生了什么(含完整报错文本,不截断) |
| 触发条件 | 什么命令/输入/环境触发;是否稳定复现 |
| 变更范围 | 问题出现前后改了什么(git status / git diff / 最近 tag 区间) |
| 环境 | shell 类型、OS、相关环境变量 |
Step 2. 复现并建立证据基线
- 按用户描述复现,收集完整报错(stderr、退出码
$?)。
- 复现不稳定时:询问触发频率与依赖条件(输入、环境变量、网络、时间)——与其余缺项一次性全部列出,不逐次追问。
- 无法复现时:要求用户提供报错全文、诊断输出和最小示例;不跳过验证。
- 多组件系统先加诊断插桩(CI→构建→签名、API→服务→数据库等多层场景,
修复提议之前):对每个组件边界核对"什么数据进入该组件、什么数据离开"、
验证环境/配置是否传播、检查各层状态;运行一次收集证据,定位在哪一层断裂,
再深入调查该组件(吸收 systematic-debugging Phase 1 证据收集)。
- 完整读取错误信息:不跳过错误与警告;栈追踪读完整,记下行号/文件路径/
错误码——它们常包含精确解法。
Step 3. 缩小范围(二分定位)
- 最近变更优先(有 git 时):
git status
git diff HEAD --stat
git log --oneline -10
- 按假设二分:
- 注释/删除嫌疑代码段,确认错误是否消失(delta-debug);
- 在关键路径加诊断输出(shell 用
bash -x 逐行跟踪;应用代码用结构化输出);
- 验证"最近改动引入"假设:
git stash 或 checkout 到上一 tag 对比行为。
- git bisect(回归型 bug 且历史较长时):
git bisect start
git bisect bad HEAD
git bisect good <最近正常tag>
git bisect run <复现命令>
- shell 脚本专有排查:
bash -x <script>
bash -n <script>
- 不变量与边界校验(物理直觉,适用时):
- 量纲/类型一致性(如时间单位混用 s/ms、数值与字符串比较);
- 边界情形:空串、0、负数、越界索引、路径含空格/中文;
- 极限行为:大输入、长路径、深递归、重复执行(幂等性);
- 环境假设:PATH、rc 文件是否被 source 干扰(对比干净环境
env -i bash 运行)。
- 模式分析(有相似代码/参考实现时):先找代码库中相似的工作代码作对照,
完整阅读参考实现(不跳行、不凭印象"适配"),逐项列出工作与损坏之间的
差异——不假设"这不可能有关系",任何差异都值得验证(吸收 systematic-debugging
Phase 2 模式分析)。
- 沿调用栈向后追溯(错误深在调用栈时,吸收 systematic-debugging
root-cause-tracing):从错误点沿调用链逐层向上回溯——"谁用坏值调用了这里?
坏值从哪来?"直至找到源头,在源头修复而非症状处打补丁;每层记录进出数据,
坏值首次出现的位置即根因候选(与二分互补:二分砍范围,追溯找源头)。
- 超时类 bug 用条件轮询替代固定超时(吸收 systematic-debugging
condition-based-waiting):不要盲目加大 sleep——轮询真实完成条件
(文件出现/端口就绪/进程退出/明确完成输出),带超时上限与失败诊断输出:
for i in $(seq 1 30); do [ -f done.flag ] && break; sleep 2; done
[ -f done.flag ] || { echo "TIMEOUT: done.flag 30s 内未出现"; ps -ef | grep <task>; }
Step 4. 确定根因
- 根因 = 能解释全部观测证据的最小原因(奥卡姆剃刀);
- 输出根因说明:发生了什么、为什么发生、受影响路径与范围;
- 多个候选根因时按证据强度排序、逐项验证;一次只修一处,不并排盲补;
- 单假设原则:每次只验证一个假设("我认为 X 是根因,因为 Y"),
测试最小改动(一次一个变量),成功→进入修复,失败→回到 Step 3 形成新假设,不叠加改动。
Step 5. 修复
- 最小改动:只改根因所在行/块;遵循代码库约定与既有 API;
- 修复与重构分离:顺手重构不属于本技能职责,除非用户要求;
- 触及系统配置(PATH、rc 文件、全局配置)前先说明意图与回滚方式,经用户同意后执行;
- 修复不得删除或覆盖用户文件,先备份或仅记录计划;
- 修复前先建立失败用例(最小复现脚本/测试),修复后用它验证闭环(红→绿),
不留"未测修复"。
Step 6. 验证
- 用原复现路径回归:错误消失、期望行为达成;
- 检查边界情形:空输入、缺参数、重复执行(幂等)、路径含空格、权限位;
- 跑仓库校验:
bash -n <改动脚本>;有测试框架则运行相关测试;
lint/typecheck 若存在必须运行;
git diff 审查:改动仅含预期内容,无引入新问题。
- 多层防御验证(吸收 systematic-debugging defense-in-depth):根因修复后,
在数据流各层(输入校验、中间处理、输出边界)确认坏值不再到达——单一位置
修复可能只堵住当前路径,其他入口同样可能把坏值带进来;验证修复处前后各层
的进出值一致。
Step 7. 总结(结构化输出)
✓ 修复完成
根因: <一句话根因>
证据: <支持的报错/诊断输出/复现>
改动: <文件:行号> <改动说明>
验证: <原复现路径回归通过; bash -n 通过; 相关测试 N 个通过>
遗留: <未处理项/风险,无则省略>
错误处理
| 场景 | 处理 |
|---|
| 无法复现 | 收集更多证据(报错全文、诊断输出、最小示例),必要时向用户提问(一次问全) |
| 报错信息不明确 | 提升诊断级别(bash -x、verbose、--debug),加打印定位 |
| 修复后错误仍在 | 回到 Step 3 检查假设是否成立,不叠加盲改 |
| 首次修复失败 | 不放弃、不盲改:基于新证据重新定位、优化方案后再次执行(循环尝试直至成功),并在终端摘要补充调试过程 |
| 连续 3 次修复失败 | 停止逐症状修复:说明架构层问题信号(每修一处暴露新耦合),与用户讨论是否重构/换方案 |
| 多次迭代仍失败 | 检查证据是否不足或假设方向错误;向用户报告进展与下一步计划,不无限循环 |
| 多个根因并存 | 逐项验证,先修证据最强的;一次只改一处 |
| 怀疑环境问题 | 对比干净环境(env -i bash)运行;检查 PATH/rc 干扰 |
| 用户发出"方向不对"信号("这不是在发生吗/停下瞎猜/我们卡住了") | 停下回到 Step 3:假设未验证、证据未收集或方向错误,从新证据重新分析 |
| 编码/权限类错误 | 检查文件编码(UTF-8)、行尾(CRLF)、权限位 |
| 修复触及系统配置 | 先说明影响与回滚方式,经用户同意后执行 |
| 仓库无测试框架 | 纯 shell 至少 bash -n + 原复现路径手动回归 |
注意事项
- 不确定时向用户提问(一次问全,不逐次追问),不臆断根因;
- 调试过程以终端摘要和验证输出为准,不创建或读取过程记录文件。