# Verify Workflow Debug

> 系统化根因调试——先建反馈循环再假设。当遇到 bug、测试失败、意外行为，或提到"调试""debug""为什么不工作""crash"

- Skill: `zeroz-lab/verify-workflow-debug` (Agent Skill)
- Install (CLI): `npx skillmds@latest add zeroz-lab/verify-workflow-debug`
- Raw SKILL.md: https://api.skillmd.com/api/skills/zeroz-lab/verify-workflow-debug/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Productivity
- Author: zeroz-lab (https://skillmd.com/u/zeroz-lab)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/zeroz-lab/verify-workflow-debug

---


# Debug — 系统化调试


## 入口/出口
- **入口**: Bug 报告、测试失败、意外行为、性能问题、构建失败
- **出口**: 复现测试通过 + `docs/bugs/<name>/01-root-cause.md`（根因记录）
- **指向**: 回到原流程（重新 build 或 review）
- **前置加载**: CANON.md + `build-quality-tdd/SKILL.md`
- **输出路径**: `docs/bugs/<name>/01-root-cause.md` → build-workflow-execute（重新 build）或 verify-workflow-review（重新 review）

## 何时不使用
- 已知行为不需要修复（已文档化的限制、预期行为）
- 环境问题简单重试可解决（网络闪断、服务重启）

## Iron Law

<HARD-GATE>
```
根因调查在前，修复在后。
```
没有完成 Phase 1，不能提出修复方案。
</HARD-GATE>

## 流程：4 阶段必须按序

### Phase 1：根因调查

**在尝试任何修复之前：**

1. **读错误信息仔细** — 不跳过错误。读完整堆栈。记下行号、文件路径、错误码。
2. **稳定复现** — 能可靠触发吗？准确步骤是？每次必现吗？不可复现 → 收集更多数据，不要猜。

3. **构建最小复现** — 去掉无关代码/配置直到只剩 bug 本身。简化输入到最小触发用例。最小复现让根因变得明显，防止修复症状而不是原因。
4. **查最近变更** — `git diff`、最近提交、新依赖、配置变更、环境差异。
5. **多组件系统加诊断埋点** — 当系统跨多个组件（CI → build → signing，API → service → DB）时：
   - 对每个组件边界：日志记录什么进入组件、什么离开组件
   - 验证环境/配置传播
   - 一次运行收集证据，显示在哪里断裂
   - 然后分析证据→定位失败组件→具体调查该组件

6. **向上追溯数据流** — 错误在调用栈深处时：从最终错误点向上追溯。错误值从哪来？谁带着错误值调用了这里？不断追溯直到找到源头。在源头修复，不在症状处修。

### Phase 2：模式分析

在确定模式后再修复：

1. **找工作示例** — 同代码库中相似的正常工作代码在哪里？
2. **和参考实现对比** — 按模式实现时，完整阅读参考实现。不跳读。
3. **识别差异** — 工作和不工作之间有什么不同？列出每一个差异，"这不重要"？这是最常见的陷阱。
4. **理解依赖** — 需要哪些组件？什么设置/配置/环境？它做出了什么假设？

### Phase 3：假设与验证

1. **形成单一假设** — 写下来："我认为 X 是根因，因为 Y"。具体不模糊。
2. **最小化测试** — 做最小的变更来测试假设。一次只变一个变量。
3. **验证通过再继续** — 确认了？→ Phase 4。没确认？→ 新假设。不要叠更多修复。
4. **不知道时说不知道** — "我不理解 X"。不要假装知道。寻求帮助。研究更多。

### Phase 4：修复

1. **创建复现测试（RED）** — 调用 `build-quality-tdd/SKILL.md` 写失败测试。先有测试再修复。**没有复现测试的修复 = 没有修复。**
2. **实现单一修复** — 处理识别出的根因。一次一个变更。没有"顺便改一下"。
3. **验证修复（GREEN）** — 测试通过了吗？其他测试没受影响？问题确实解决了？
4. **如果修复不工作** — STOP。数一下试了几次。
   - < 3 次：回到 Phase 1 用新信息重新分析
   - **≥ 3 次：冻结。进入 Phase 4.5**

### Phase 4.5：架构质疑门

**连续 3 次修复失败 = 架构问题：**

迹象：
- 每次修复都暴露新的共享状态/耦合/不同位置的问题
- 修复需要"大规模重构"才能实施
- 每次修复在其他地方引发新症状

**STOP 并质疑基础：**
- 这个模式从根本上成立吗？
- 我们是在"因为惯性而坚持它"吗？
- 重构架构 vs. 继续修复症状？

**与人类讨论后再尝试更多修复。这不是假设失败——这是错误的架构。**

## 错误类型专门诊断

### 测试失败

```
测试在代码变更后失败：
├── 代码被测试覆盖了？
│   └── YES → 测试还是代码错了？
│       ├── 测试过时 → 更新测试
│       └── 代码有 bug → 修复代码
├── 改了不相关的代码？
│   └── YES → 副作用 → 检查共享状态、import、全局变量
└── 测试本来就 flaky？
    └── 检查时序问题、顺序依赖、外部依赖
```

### 构建失败

```
构建失败：
├── 类型错误 → 读错误信息，检查对应位置类型
├── Import 错误 → 模块存在？exports 匹配？路径正确？
├── 配置错误 → 检查构建配置文件的语法/schema
├── 依赖错误 → 检查 package.json，重装依赖
└── 环境错误 → Node 版本、OS 兼容性
```

### 运行时错误

```
运行时错误：
├── TypeError: Cannot read property 'x' of undefined
│   └── 某个值不该 null/undefined → 向上追溯数据流
├── 网络错误 / CORS
│   └── 检查 URL、headers、服务端 CORS 配置
├── 渲染错误 / 白屏
│   └── 检查 error boundary、console、组件树
└── 意外行为（无错误）
    └── 关键路径加日志，验证每一步数据
```

## Safe Fallback 模式

时间压力下使用安全降级，不崩溃：

```typescript
// 安全默认 + 警告（不崩溃）
function getConfig(key: string): string {
  const value = process.env[key];
  if (!value) {
    console.warn(`Missing config: ${key}, using default`);
    return DEFAULTS[key] ?? '';
  }
  return value;
}

// 优雅降级（不展示破碎功能）
function renderChart(data: ChartData[]) {
  if (data.length === 0) {
    return <EmptyState message="暂无数据" />;
  }
  try {
    return <Chart data={data} />;
  } catch (error) {
    console.error('Chart render failed:', error);
    return <ErrorBoundaryFallback />;
  }
}
```

## 好坏示例

### Good — 系统化根因 + 修复验证
Phase 1 读错误信息 + 稳定复现 + 最小复现 → Phase 2 找工作示例对比差异 → Phase 3 假设"N+1 查询是根因" → Phase 4 写复现测试（RED）→ 修复 → 测试通过（GREEN）。根因可追溯，修复可验证。

### Bad — 随机试错法
"试试改这个"、"再改那个"、"改三个地方一起跑"。没有读错误信息、没有复现步骤、没有单一假设、没有复现测试。修了症状不知道根因，同类 bug 在其他位置复发。

## 输出模板

- Phase 1-3 使用 `templates/bug/01-root-cause.md`，落盘到 `docs/bugs/<name>/01-root-cause.md`
- Phase 4 使用 `templates/bug/02-fix-plan.md`，落盘到 `docs/bugs/<name>/02-fix-plan.md`

根因记录必须包含：Status Summary、症状、影响范围、时间线、复现步骤、复现证据、调查过程、根因、非根因排除、修复方向、Done When。

修复计划必须包含：Status Summary、修复目标、最小改动范围、复现测试、修复步骤、验证计划、回归风险、Follow-up Actions、Done When。

## 相关技能
- 写复现测试 → `build-quality-tdd/SKILL.md`
- 验证修复 → CANON 第 5 条（Verify Don't Assume）

## 验证证据

输出或记录必须包含：
- **输入/来源**: 读取的 spec、plan、代码、反馈或发布上下文。
- **执行动作**: 实际完成的检查、生成、修复、导出或发布步骤。
- **验证结果**: 命令、审查结论、产物路径、截图或人工确认。
- **阻塞/回退**: 未通过项、回退路径或需要 human partner 决策的问题。

## 常见说辞

| 说辞 | 现实 | 后果 |
|------|------|------|
| "快速修复，之后调查" | 没有之后。先在根因，再修复。 | 修症状不修根因，同类 bug 在 3 个不同位置反复出现，累计修复时间 10x 于单次根因调查。 |
| "先改改看行不行" | 猜。先确定根因。 | 猜测式调试平均浪费 45 分钟（行业数据），系统化调试平均 15 分钟。每次猜错都在掩盖真实线索。 |
| "改多个地方一次跑" | 无法隔离有效变更。 | 两个变更互相干扰，通过纯属巧合。下一次只改其中一个时故障复现，且无法判断是哪个变更"真正"修复了问题。 |
| "跳过测试，手动验证" | 手动测试不能证明边界情况。 | 手动验证遗漏的边界条件（并发、空值、超时）以生产偶发故障形式出现，排查需 2-8 小时。 |
| "紧急情况没时间走流程" | 系统化调试比猜更快。 | 紧急中猜测式修复引入新 bug 的概率 ~40%，二次事故的停机损失 > 系统化调试多花的 10 分钟。 |
| "再试一次就好"（第 3+ 次） | 3 次失败 = 架构问题。质疑，不继续猜。 | 第 4、5、6 次尝试不会比前 3 次更好。每次失败都引入更多不确定性，最终不得不全部回退，浪费时间且代码更乱。 |

**违反字面规则就是违反精神。** 没有灰色地带。

## 验证失败处理

| 失败场景 | 处理方式 |
|---------|---------|
| 无法复现 bug | 收集更多数据（日志、监控、用户上下文），不要猜测。不可复现 = 不能确定修复 |
| 修复后测试仍失败 | STOP。数一下尝试次数。< 3 次 → 回 Phase 1 重新分析；≥ 3 次 → 进入 Phase 4.5 架构质疑门 |
| 修复引入回归 | 回退修复，重新分析根本原因和副作用 |
| 找不到工作参考示例 | 扩展搜索范围到同技术栈的其他项目，或寻求人类指导 |
| 复现测试本身有缺陷 | 修复测试，确保 RED→GREEN 循环有效。测试没错之前不要修代码 |

## 红旗 — STOP 走流程

<HARD-GATE>
如果发现自己想：
- "快速修复，之后调查"
- "先试试改 X"
- "改多个地方一次跑测试"
- "跳过测试，手动验证"
- "大概是 X，让我修"
- 提出修复方案前还没追溯数据流
- "最后一次尝试"（已经试过 2+ 次）
- 每次修复暴露不同位置的新问题

**全部意味着：STOP。回到 Phase 1。**
</HARD-GATE>

**注意来自人类伙伴的信号：**
- "是不是没发生？" — 你假设了但没验证
- "能不能...看看？" — 你加诊断证据
- "别猜了" — 你在没理解根因的情况下提修复方案
- "我们又卡住了？"（沮丧）— 你的方法不对

**全部意味着：STOP。回到 Phase 1。**

## 验证清单

- [ ] 根因已确定（不是猜测）
- [ ] 出错误信息已完整读取和理解
- [ ] 有稳定复现步骤
- [ ] 写了一对复现测试（RED→GREEN 验证）
- [ ] 修复针对根因，不是症状
- [ ] 所有测试通过 + 无回归
- [ ] 根因记录保存到 `docs/bugs/<name>/01-root-cause.md`
- [ ] 修复计划保存到 `docs/bugs/<name>/02-fix-plan.md`

