关于 root-cause
Root Cause 是一项规范技术调试流程的 Agent 技能,以可复现证据、排序后的假设和人工因果审查为基础。它要求先建立失败信号并缩小复现范围,再进入修复,而非猜测后打补丁。
主要功能
- 01调查前建立失败信号
- 02复现并缩小问题范围
- 03排列可证伪假设
- 04要求人工判定因果关系
- 05修复前添加回归测试
- 06验证修复并清理观测代码
适用场景
- 01调查可复现的软件缺陷
- 02追踪跨系统边界的故障
- 03分析提供的性能或事件记录
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ jayden-dang/ skills --skill root-cause安装后在 Claude Code 中输入/root-cause确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
eval.jsonexternal-dependency-evidence.mdTESTS.md详细介绍
根因
铁律
NO FIXES WITHOUT ROOT CAUSE INVESTIGATION FIRST
NO AGENT-AUTHORED AUTHORITATIVE CAUSAL ACCEPTANCE
猜测并打补丁会浪费数小时,还会埋下新的 Bug。此流程适用于所有技术问题——尤其是那些最容易让人想跳过流程的情况:紧急事件、“显而易见”的单行修复,以及上一次修复无效之后。
因果关系的确认只能由人来完成——参见因果判断(唯一指定位置)。
阶段 1 — 建立反馈闭环(门槛)
如果报告的问题发生在已部署的环境中(生产、预发布、远程开发),且目前还没有远程证据包:必须先使用 debug-remote 子技能。该证据包就是阶段 1 所需的信号。不要通过 kubectl exec 或向生产环境重放会修改数据的请求来建立闭环。
在构建任何理论之前,先构建并运行一个可呈现失败的信号:一条命令——测试调用、脚本或 curl——它现在会因为这个确切的 Bug 而失败,并会在 Bug 修复后通过。建立正确的反馈闭环,Bug 就解决了 90%;后续各阶段只需利用这个闭环。
如果你发现自己在这条命令出现之前就开始阅读代码、构建理论,请停下来。没有可呈现失败的命令,就不要进入阶段 2。
构建此命令的策略,按大致优先级排列:
- 能触及 Bug 所在环节的失败测试(单元测试、集成测试、端到端测试)
- 针对运行中的开发服务器执行 curl / HTTP 脚本
- 使用测试输入调用 CLI,并与已知正确的输出进行差异对比
- 无头浏览器脚本,断言 DOM、控制台或网络行为
- 将捕获的审计跟踪(已保存的请求、负载、事件日志)重放到代码路径中
- 临时测试框架:使用系统的最小切片来触发 Bug 路径
- 当输出“有时不正确”时,对随机输入进行属性测试 / 模糊测试循环
- 当 Bug 出现在两个已知状态之间时,使用二分测试框架(
git bisect run) - 差异对比循环:将相同输入分别传给旧版和新版,并比较输出
- 人机协作脚本——最后的手段,仅适用于必须由人亲自操作的情况;将操作步骤编写成脚本,让循环保持结构化
然后收紧它:提高速度(缓存初始化、缩小范围——以秒计,而非分钟)、提高精度(断言用户遇到的确切症状,而不只是“没有崩溃”)、确保确定性(固定时间、随机种子,隔离文件系统)。遇到非确定性 Bug?不要执着于稳定复现;应提高复现率(将触发条件循环 100 次、增加压力、缩短时间窗口),直到复现率足以支持根因分析。
确实无法构建此命令?请明确说明,列出你尝试过的方法,并请求用户提供可复现的环境、捕获的产物,或允许添加临时插桩。不要凭感觉继续。
当你拿到的证据本身就是产物时——例如事后提供的 cpuprofile、堆快照、spindump、跟踪记录或捕获的事件日志——就无法构建可呈现失败的命令;而要求对方提供你手头已有的捕获产物,也会形成循环。该产物就是反馈闭环。读取它,将其缩减到承载该症状的单个栈帧、保留链或阻塞线程,并将其追溯到对应的文件和符号。交付内容是带引用依据的诊断,而非修复方案:将诊断交还,并在有人能将症状表述为一条会失败的命令后,重新进入阶段 1。
**门槛检查:**说出命令,并贴出一次运行产生的失败输出。
阶段 2 — 复现并精简
运行循环;观察它因用户描述的故障而变红(出现附近但不同的故障 = 错误的 Bug = 错误的修复)。阅读完整错误信息 — 完整消息、完整堆栈跟踪、行号;错误中往往就包含答案。检查最近的更改:git diff、新依赖、配置、环境。
每次只精简一项(输入、配置、调用方、步骤),每次精简后都重新运行,直到 每个剩余元素都不可或缺 — 移除任何一项都会让测试变绿。最小复现可以缩小假设范围,并成为回归测试。
对于多组件系统(CI → 构建 → 部署、API → 服务 → DB):在每个边界添加监控 — 记录进入和离开每一层的内容 — 并先运行一次,查看故障发生在何处,再动手修复。当错误在调用链深处出现时,沿调用方审计追踪错误值,回溯到最初的触发因素。不要只修复错误出现的位置;那是在治标不治本。
检索不是能触发失败的循环。 在阶段 1–2 期间,不要使用
load-subgraph / 检索作为反馈信号。只有能触发失败的命令才是进度门槛。
阶段 2 之后 — 功能归属上下文(阶段 3 之前)
阶段 2 完成后,当你已有一个 路径或稳定术语,并且在阶段 3 提出假设之前:必须使用所需子技能:使用 load-subgraph 获取归属 / 邻接上下文(选择路径 / 术语作为种子;根据情况使用 neighbors 或 blast_radius)。有依据的声明(统一规范):遵循 skills/execution/load-subgraph/references/grounded-claims.md — 绝不要根据概述臆造根因假设;即使邻接信息看起来很有暗示性,也绝不要跳过失败循环。
阶段 2 之后的运维文档(可选 — 绝不替代失败循环)
仅在阶段 2 之后(阶段 1–2 仍要求先运行能触发失败的命令)。加载: skills/project/define-system-doc/consult-recipe.md。适用时的路径: docs/ops/runbooks.md(以及运行手册的 slug)、docs/ops/observability.md、docs/ops/disaster-recovery.md — 仅作为阶段 3 及之后的参考上下文,绝不替代失败循环。若文档不存在则不执行任何操作;如有实质帮助,建议一次 /define-system-doc ops/runbooks|observability|disaster-recovery;绝不自动调用。
阶段 2 之后的外部依赖证据(有条件 — 阶段 3 之前)
如果最小化后的故障路径涉及有版本的外部依赖 — 库、框架、SDK、数据库、缓存、搜索 / 可观测性平台、云服务、外部 API、CLI、提供方发行版或协议 — 那么必须在编写假设前完成此证据门槛。阶段 1 信号和阶段 2 最小复现仍是门槛;文档绝不能替代二者。
满足适用条件时,请阅读此文件旁边的 external-dependency-evidence.md 并严格遵循:运行时身份、通过 research 查找所属文档、契约差异表、出现分歧时检查历史记录,以及声明状态 — 每一步都必须以阶段 1 的失败输出为门槛;若无法获取运行时证据,则逐字记录未解决的处置结论。
第 3 阶段 — 提出假设
在测试任何假设之前,先按优先级列出 3–5 个假设(单个假设会让你锚定在第一个看似合理的想法上)。每个假设都必须可证伪,并明确写出预测:“如果 X 是原因,那么更改 Y 就会让 Bug 消失”。如果无法写出预测,那就只是凭感觉——请把它说得更具体,或将其舍弃。向用户展示排序后的列表(他们通常会立即重新排序);如果用户不在,不要因此停下。
先测试最小的假设。每次只改变一个变量——绝不要叠加更改。
运行时检查——这是本阶段中的一项区分性实验,不能替代第 1 阶段。使用假设中明确写出的预测。连接进程不等于接受因果关系(参见“因果判定”)。
当被测试的进程位于本地环境或专用检出副本中(已有红色信号和最小复现):优先使用调试器、REPL 或 DAP 会话,而不是大量刷日志——一个断点或监视表达式胜过十条打印语句。仅限浏览器的问题也可以同样使用 DevTools/CDP。记录:
## Inspection evidence
- Prediction: <from the hypothesis under test>
- Tool: <debugger | REPL | DAP | DevTools/CDP | profiler | [DBG-…] log>
- Commands: <literal>
- Observation: <frame / locals / snapshot / paste>
- Outcome: <confirms | falsifies | inconclusive>
当故障只存在于共享的已部署环境中时:不要在那里执行 exec 或连接进程——这类边界属于 debug-remote。在本地或专用副本上运行第 3 阶段的探测,或仅使用不会改变状态的证据。
否则(没有可用的调试器,或日志更合适):针对性地记录日志,并为每次探测使用唯一前缀(例如 [DBG-x7q2]),这样只需一次 grep 即可清理——绝不要记录所有内容再用 grep 筛选。
性能或内存类 Bug:先测量基线(使用分析器、消毒器或计时工具),然后再做二分排查;日志在这类问题上会误导你。
有不理解的地方?就说“我不理解 X”,然后调查——绝不要装懂并猜测。
假设被证伪?划掉它,继续下一个。不要在失败的假设之上再叠加新的修复。
因果判定——在权威确认前必须完成
将调查状态(open / unresolved / falsified / superseded)与请求的强度(candidate / probable_contributor / confirmed_for_scope)分开处理。请求的强度是一项命题,不是已接受的结论。
## Causal disposition request
- Proposition: <one exact causal claim>
- Investigation state: <open | unresolved | falsified | superseded>
- Requested strength: <candidate | probable_contributor | confirmed_for_scope>
- Scope: <reproduced condition / environment bound>
- Support set: <commands, outputs, artifacts that bear on this proposition>
- Contradictions / alternatives still live: <none | list>
- Ask: accept / reject / supersede (eligible human only)
不要在提交、PR
或结项说明中写入由 Agent 权威认定的 `confirmed root cause`。不要擅自将 `probable_contributor` / `confirmed_for_scope`
判定为 Agent 的处置结果。不要把 Exit、已填写的说明或文字版
LGTM 视为人类已接受该命题。
操作成功(回滚、缓解、错误率恢复)不会提升已接受的因果强度。
第 1–3 阶段通过 = 调查已准备好请求判定。已确认 = 符合资格的人类已接受该命题。二者并不相同。
阶段 4 — 修复
- 先编写失败的回归测试(必需子技能:使用
test-first),在处置请求中提出命题后进行。测试必须放在正确的接缝处——能够复现实际发生的 bug 模式。如果不存在正确的接缝,这本身就是一项发现:记录下来并标记为供事后复盘;在浅层接缝处进行测试会带来虚假的信心。 - 在将生产代码作为纠正性修复合入之前,必须由人工接受该确切命题(力度 + 范围 + 支持材料集)。在 Slack 上对文字回复一句“发吧”,或觉得阶段 1–3 已经完成,都不算接受。在会话中,用户明确接受所述命题即算接受。**延后接受:**如果无人值守调用方的说明中写明将处置决定留待结案报告(
run-dogfood就是如此),则应用修复并在该未合并分支上提交,标记为pending disposition。运行过程中不要询问。该请求会出现在调用方的结案报告中;在符合条件的人工接受之前,不得合并修复。 - 只做一个修复,用于解决已接受的命题。不要顺手改进,也不要捆绑重构。
- 观察回归测试通过,重新运行完整测试套件,并针对原始未缩减场景重新运行阶段 1 的循环。
修复尝试失败 3 次 = 停止。 需要质疑的是架构,而不是你最新的假设——尤其是在每次修复都揭示其他地方存在新的耦合时。第 4 次尝试前先与用户讨论。
退出 — 精简规格、清理、事后复盘
- Tier-1 精简规格:在所属功能的
requirements.md(如果没有功能负责,则在docs/specs/fixes.md)中添加修复需求和SHALL CONTINUE TO防护需求。在任务报告 / 规格审查中将回归测试映射到该 ID——仅文档主干;对于消费端应用,不要要求在测试源码中包含可搜索的 ID。 - 移除所有检测代码:搜索你的
[DBG-...]前缀;删除临时测试工具。 - 在提交 / PR 中说明命题、请求的或人工接受的力度、范围和支持材料指针。如果尚无人做出处置决定,请写明
requested/pending disposition。绝不可用confirmed root cause: …代替 Agent 的接受。 - 路由任务:“什么本可以避免这个 bug?”如果答案涉及架构(没有合适的接缝、存在隐藏耦合、调用方关系错综复杂),请写下具体情况,并告知用户运行
/scan-architecture——在修复合入后运行,此时你掌握的信息最多。 - 必需子技能:在声称 bug 已修复之前,使用
prove-claim。
合理化借口
| 想法 | 现实 |
|---|---|
| “情况紧急,没时间走流程” | 找出根因比盲目反复尝试更快 |
| “显然是 X,我直接修一下就行” | 看到症状不等于理解原因 |
| “先快速打个补丁,之后再调查” | 补丁会变成永久方案,根因还会再次引发问题 |
| “一次尝试多个改动,节省时间” | 你无法判断哪个改动奏效,其中一个还可能导致问题 |
| “问题太简单,不需要复现” | 简单的 bug 也有根因;整个循环只需几分钟 |
| “修好后再添加回归测试” | 未经测试的修复会导致回归;失败的测试正是修复有效的证明 |
| “再试一次”(失败 2 次以上后) | 没有架构讨论就进行第 4 次尝试,只是在盲目折腾 |
| “先加载功能子图——这就是整个循环” | 阶段 1–2 需要能复现失败的命令;只有阶段 2 之后才进行检索 |
| “邻接卡片写了负责人——跳过最小化复现” | 阶段 2 之后,所有权信息仅供参考;最小化复现仍是进入阶段 3 的门槛 |
| “我熟悉这个库;等找到可能的修复后再查文档” | 在提出外部行为假设之前,必须先确认运行时身份并查阅版本匹配的所属文档 |
| “退出项要求写明已确认的根因——那条提交说明就是验收” | 退出项记录的是命题和处置状态;只有符合条件的人类才能接受该命题 |
| “阶段 1–3 已完成;人类处置只是形式,本仓库不写” | 铁律禁止由 Agent 撰写权威验收结论;处置请求是书面门槛 |
| “资深员工看过说明,说 LGTM 了/直接发出退出项吧” | 对文字说 LGTM ≠ 针对一个确切命题及其支持依据作出接受决定 |
| “回滚成功了——升级为已确认” | 缓解成功属于运维层面;它不会提高因果结论的可信程度 |
| “我接入了调试器,所以原因已确认” | 接入调试器是一项实验;是否接受仍取决于人类处置 |
| “生产/预发布环境——直接用 lldb/exec 查看局部变量” | 共享的已部署环境只能通过 debug-remote 进行只读操作;探查应从本地开始 |
危险信号 — 停止
- 准备在提交或 PR 中写入由 Agent 撰写的
confirmed root cause - 准备把阶段 4/退出项当作人类因果验收
- 准备因为回滚成功或错误率恢复就提高因果结论的可信程度
- 准备因为“技能说明以前写着 confirmed”就跳过处置请求
- 准备在共享的已部署环境中接入或执行调试器
- 准备把“我们用了调试器”视为原因已获接受
用户信号 — 返回阶段 1
| 用户说 | 含义 |
|---|---|
| “别再猜了” | 你在没有根因的情况下提出了修复方案——返回阶段 1 |
| “这真的发生了吗?” | 你在未验证的情况下做了假设——收集证据 |
| “这能让我们看到什么吗?” | 你的探查没有对应任何预测——重新表述假设 |
| “我们在原地打转” | 数一数失败的修复尝试;你可能已经到达架构关卡 |
| “谁接受了这个原因?” | 你自行认证了结论——发起处置请求 |