# Debug Root Cause

> 排错纪律。报错、测试失败、行为不符预期、偶发问题时使用；先复现再定位，找到根因再修，不猜、不连试三次、不用 retry / try-catch / 重启掩盖；修完说明根因在哪一层、为什么之前没拦住。

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

---

# debug-root-cause：先复现，再动手

## 何时用
任何"不对劲"：报错、测试红、结果不符、偶尔失败。

## 硬规则
- MUST 先复现：拿到能稳定触发的最小步骤或输入；复现不了就先说复现不了，不修。
- MUST 定位到具体位置（文件、函数、哪一步的状态不对）再改代码；定位靠读代码、加日志、缩小范围，不靠改一处试一次。
- NEVER 连续三次"改了试试"；第二次不中就停下重新定位。
- MUST 区分根因与症状：修的是根因；只能先止血时明说"这是止血，根因是 X，待修"。
- NEVER 用 retry、try/catch 吞异常、sleep、重启、清缓存当作修复；它们掩盖竞态与状态问题（见 concurrency-modeling）。
- MUST 修完加回归检查：一条测试或一条可重跑的验证命令，保证同样的错再出现时能被抓住。
- MUST 修完说明根因与所在层（输入校验 / 状态 / 并发 / 依赖 / 环境）；之前有检查却没拦住的，说明为什么；需要补守卫的，说补什么（见 guardrails）。

## 审问清单
1. 我能让它稳定再错一次吗？
2. 出错时哪个变量、哪个状态和预期不一样？在哪一行第一次不一样？
3. 我现在这个改法，是知道为什么，还是在试？
4. 修完了，同样的错还能不能悄悄回来？
5. 这个 bug 该在哪一层被拦住？

## 反模式
- 错误：报 `KeyError: 'user_id'`，加 `.get('user_id', None)`。→ 正确：查为什么这条请求没有 `user_id`——上游哪一步漏了；漏的地方修，入口加校验。
- 错误：偶发 500，加 `retry(3)`。→ 正确：抓到失败时的输入和状态，多半是并发或脏数据，按根因建模。
- 错误：改了五处，最后不知道哪处起作用。→ 正确：一次改一处，不中就回退。
- 错误："修好了。" → 正确："根因：订单状态在支付回调前被定时任务改成 expired；层：并发；补了状态机转移约束和回归测试 `test_callback_after_expire`。"

## 输出要求
定位阶段：复现步骤 + 首次异常位置。修复后：根因一句、所在层、回归检查命令；为什么没拦住与补什么守卫，仅在适用时写。

