# Root Cause First

> 面对难缠的 bug、无声的失败、回归排查，或一个可能悄悄弄坏下游调用方的高风险改动时用它。不调查就没有修复——读报错、按需复现、查最近的改动、在组件边界上打探针、沿数据流倒推到源头。Trigger words: debug, root cause, why is this failing, silent failure, regression, works in tests but fails live, systematic debugging. 中文触发词：根因、排查、为什么会挂、无声失败、回归、测试过了线上挂、系统化调试、先查再修。

- Skill: `tcuzzo/root-cause-first-7` (Agent Skill)
- Install (CLI): `npx skillmds@latest add tcuzzo/root-cause-first-7`
- Raw SKILL.md: https://api.skillmd.com/api/skills/tcuzzo/root-cause-first-7/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- License: MIT
- Author: Tcuzzo (https://skillmd.com/u/tcuzzo)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/tcuzzo/root-cause-first-7

---


# Root Cause First
**Effort:** free — 纯调查纪律，通常还直接降低净成本：一根决定性探针，替掉“把整条流水线点着看看会怎样”。消除：修错对象的补丁——那种藏起真 bug、还弄坏下游的症状修复。

不调查就没有修复。在理解失败之前打的补丁，修的是错的东西，藏起真正的 bug，还会弄坏下游。你的产品不是一块补丁——是一个由决定性探针证明的根因，加上一个被证明不带任何回归的修复。

下面的一切由两条法则统辖：

1. **不做假设——代码、数据和实况系统才是真相；笔记只是线索。** 一条注释、一段记忆、一个先前的结论，甚至你自己的上一句话，在探针确认之前都只是假设。"所有 / 每个 / 没有"这类字眼触发三点检查：环境、全仓库搜索、扫遍每一个调用方。
2. **一个被验证的反例，立即击杀先前的结论。** 探针推翻了你相信的东西，就直说："我错了——实际是 X"，然后从新事实继续。绝不粉饰过去。

## 循环（按顺序跑；不许跳步）

1. **读报错。** 用一句精确的话说出症状。读实际的报错信息，不是你以为它会说什么。点出爆炸半径：你怀疑的那个东西，都有谁依赖它？
2. **复现。** 让失败随叫随到——在实况里，或在一条失败的测试里。**掐个时间。**真实工作要花几秒、"失败"却毫秒级就返回，说明是早早被吞掉的异常，不是真实工作在失败。时间差本身就是线索。
3. **查最近的改动。** diff 出上次正常以来变了什么——代码、配置、环境、依赖。历史太长就二分。
4. **画出调用方地图。** 共享面上的 bug，列出每个调用方以及各自的用法（精确字符串匹配？布尔？列表？）。真正的回归通常藏在下游某处的精确匹配比较里，不在你正拧的那个旋钮上。
5. **在边界上打探针。** 在每条组件接缝上打日志或探针——进什么、出什么。沿着坏数据一个边界一个边界往回追，直到源头。修源头，绝不修症状。
6. **靠假设找根因。** 提出一个可证伪的假设。找到能把它与其他解释分开的那一个决定性探针，只跑这一个。别为了"看看会怎样"把整条流水线点着。
7. **在对的接缝上外科手术式地修。** 解决根因的最小改动。优先修那个唯一的共享源头（一个规范化器、一个执行器），而不是改 N 个调用点。可能的话让修复在正常路径上惰性——可证明它在那里什么都不改，只在坏的路径上生效。不许捎带相邻的重构。
8. **证明它。** 写出复现这个 bug 的失败测试；看它变红；修；看它变绿。然后跑第 4 步画出的每条调用路径的测试——那里的绿就是你的零回归地板。mock 了恰好失败的那条接缝的套件什么都证明不了。
9. **实况验证。** 开动真实系统——真实请求、真实数据库、真实日志。绝不用一个把代码 import 进自己进程的旁挂脚本。留下前后对比证据。
10. **沉淀。** 写下症状、决定性探针、根因，以及把它藏起来的反模式，让下一个同形状的 bug 变便宜。

## 先搭复现循环，再谈理论

发现自己在"红色可复现命令"存在之前就读代码搭理论——停。没有能变红的命令，就没有理论。一个在这个 bug 上会变红的紧凑通过/失败信号，是调试里最大的单项增益。在这里花不成比例的力气。

搭建方式，大致按序：一条失败测试；一个打向 dev 服务器的 HTTP 脚本；用固定输入跑 CLI、与已知良好快照做 diff；无头浏览器脚本；抓一份真实载荷、隔离重放进代码路径；一个只调一个函数的一次性脚手架；随机输入的 fuzz 循环；让自动二分能跑的二分脚手架；差分循环（同一输入过新旧两版，diff 输出）。

然后把它拧紧：更快（缓存准备工作、收窄范围）、更锐（断言具体症状，而不是"没崩"）、确定性（钉死时间、固定随机种子、冻结网络）。一个两秒的确定性循环是超能力。

对付偶发 bug，追求更高的复现率，而不是干净的复现：把触发器循环 100 次、加压、收窄时间窗。50% 的偶发能调；1% 的偶发不能。

真的搭不出循环，就停下来直说。列出你试过的，向你的人请求访问权限、一份抓取的产物，或临时插桩。没有循环就不搭理论。而且，如果不存在任何能复刻真实调用模式的接缝，这个"缺失"本身就是一个发现——修复落地后，把这个架构缺口标出来。

## 反模式（难缠的 bug 靠这些活着）

- 凭一条笔记或注释下结论，没打探针。
- 没复现就修。
- 相信一个 mock 了实况恰好失败那条接缝的绿色套件。
- 旁挂验证——import 代码，而不是开动实况系统。
- 没画出精确匹配的下游调用方，就去拧配置旋钮。
- 修复捎带大范围重构。
- 没做三点检查就说"所有 / 每个 / 没有"。

## 搭配使用

- [red-first](../red-first/SKILL.md) — 修复之前先 commit 那条失败测试。
- [sniper-testing](../sniper-testing/SKILL.md) — 迭代时只跑限定范围的测试。
- [seam-engineering](../seam-engineering/SKILL.md) — 修掉一类，不是一例。
- [repair-loop](../repair-loop/SKILL.md) — 完整的修复加落地周期。

> 脚手架致谢：Matt Pocock, diagnosing-bugs (mattpocock/skills)。此处的组合方式与硬性规则属于 BACKS AIOS。

