
Systematic Debugging
- 919 installs
- 7.3k repo stars
- Updated July 23, 2026
- jnmetacode/superpowers-zh
systematic-debugging is a Chinese-language agent skill that enforces a four-phase root-cause investigation ritual before any bug fix is proposed for developers facing test failures, production defects, or unexplained beh
About
systematic-debugging is a Superpowers-zh agent skill that blocks guess-and-patch debugging. Its iron rule requires completing root-cause investigation before proposing any fix, covering test failures, production bugs, performance issues, build failures, and integration problems. The workflow runs four sequential phases—root-cause investigation, hypothesis formation, targeted verification, and only then fix implementation—so agents do not mask symptoms with hasty patches. Developers reach for systematic-debugging under time pressure, after failed prior fixes, or when a bug looks trivial but keeps recurring. The skill is written in Chinese and mirrors the disciplined debugging methodology from the Superpowers skill family.
- Iron law: no fix proposal until Phase 1 root-cause investigation is complete
- Four mandatory phases: investigation, hypothesis, fix, verification
- Requires stable reproduction and full stack-trace review before changes
- Multi-component boundary logging template for CI, build, signing, and API stacks
- Explicitly mandatory under time pressure and after failed prior fixes
Systematic Debugging by the numbers
- 919 all-time installs (skills.sh)
- +42 installs in the week ending Jul 28, 2026 (Skillselion tracking)
- Ranked #51 of 610 Debugging skills by installs in the Skillselion catalog
- Security screen: HIGH risk (skills.sh audit)
- Data as of Jul 28, 2026 (Skillselion catalog sync)
npx skills add https://github.com/jnmetacode/superpowers-zh --skill systematic-debuggingAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 919 |
|---|---|
| repo stars | ★ 7.3k |
| Security audit | 2 / 3 scanners passed |
| Last updated | July 23, 2026 |
| Repository | jnmetacode/superpowers-zh ↗ |
How do you find root cause before fixing bugs?
Who want a disciplined, four-phase debugging ritual (Chinese Superpowers) before any fix is proposed for bugs, test failures, or odd behavior.
Who is it for?
Developers or coding agents tempted to patch symptoms after test failures, flaky builds, or recurring production defects.
Skip if: Greenfield feature implementation with no defect present, or tasks needing only code generation without investigation.
When should I use this skill?
Any bug, test failure, build error, performance anomaly, or integration issue appears and a fix is being considered before root cause is confirmed.
What you get
A documented root-cause analysis, verified hypothesis, and evidence-backed fix plan completed across four debugging phases.
- root-cause report
- reproduction steps
- verified fix plan
By the numbers
- Defines a 4-phase debugging ritual before any fix is proposed
Files
系统化调试
概述
随意修复既浪费时间又会引入新 bug。草率的补丁只会掩盖深层问题。
核心原则: 在尝试修复之前,务必先找到根本原因。只修症状就是失败。
敷衍走流程等于违背调试的精神。
铁律
不做根因调查,不许提修复方案如果你还没完成第一阶段,就不能提出修复方案。
何时使用
用于任何技术问题:
- 测试失败
- 生产环境 bug
- 异常行为
- 性能问题
- 构建失败
- 集成问题
尤其在以下情况必须使用:
- 时间紧迫(紧急情况最容易让人猜测式修复)
- 觉得"一个小修改"就能搞定
- 已经尝试了多种修复
- 上一次修复没有生效
- 你没有完全理解问题
以下情况也不要跳过:
- 问题看起来很简单(简单的 bug 也有根本原因)
- 你很赶时间(越急越容易返工)
- 领导要求立刻修好(系统化调试比反复尝试更快)
四个阶段
你必须完成每个阶段后才能进入下一个。
第一阶段:根因调查
在尝试任何修复之前:
1. 仔细阅读错误信息
- 不要跳过错误或警告
- 它们往往直接包含解决方案
- 完整阅读堆栈跟踪
- 记下行号、文件路径、错误码
2. 稳定复现
- 你能可靠地触发它吗?
- 具体的复现步骤是什么?
- 每次都能复现吗?
- 如果无法复现 → 收集更多数据,不要猜测
3. 检查近期变更
- 什么变更可能导致了这个问题?
- git diff、最近的提交
- 新依赖、配置变更
- 环境差异
4. 在多组件系统中收集证据
当系统有多个组件时(CI → 构建 → 签名,API → 服务 → 数据库):
在提出修复方案之前,先添加诊断埋点:
对每个组件边界:
- 记录进入组件的数据
- 记录离开组件的数据
- 验证环境/配置的传递
- 检查每一层的状态
执行一次以收集证据,确定断裂点在哪里
然后分析证据,定位故障组件
然后针对该组件深入调查示例(多层系统):
# 第 1 层:工作流
echo "=== Secrets available in workflow: ==="
echo "IDENTITY: ${IDENTITY:+SET}${IDENTITY:-UNSET}"
# 第 2 层:构建脚本
echo "=== Env vars in build script: ==="
env | grep IDENTITY || echo "IDENTITY not in environment"
# 第 3 层:签名脚本
echo "=== Keychain state: ==="
security list-keychains
security find-identity -v
# 第 4 层:实际签名
codesign --sign "$IDENTITY" --verbose=4 "$APP"由此可以看出: 哪一层出了问题(secrets → workflow ✓, workflow → build ✗)
5. 跟踪数据流
当错误发生在调用栈深处时:
参见本目录下的 root-cause-tracing.md,了解完整的反向追踪技术。
简要版本:
- 错误值从哪里产生的?
- 谁用错误值调用了这里?
- 持续向上追踪直到找到源头
- 在源头修复,而不是在症状处修复
第二阶段:模式分析
先找到模式,再修复:
1. 找到可正常工作的示例
- 在同一代码库中找到类似的正常代码
- 有什么正常的代码与出问题的代码相似?
2. 与参考实现对比
- 如果是实现某个模式,完整阅读参考实现
- 不要略读——逐行阅读
- 在应用之前彻底理解该模式
3. 识别差异
- 正常代码和出问题的代码之间有什么不同?
- 列出每一个差异,无论多小
- 不要假设"那不可能有影响"
4. 理解依赖关系
- 这个功能需要哪些其他组件?
- 需要哪些设置、配置、环境?
- 它有哪些隐含假设?
第三阶段:假设与验证
科学方法:
1. 提出单一假设
- 清晰地陈述:"我认为 X 是根本原因,因为 Y"
- 写下来
- 要具体,不要含糊
2. 最小化测试
- 做出最小的改动来验证假设
- 每次只改一个变量
- 不要同时修复多个问题
3. 继续之前先验证
- 生效了?是 → 进入第四阶段
- 没生效?提出新假设
- 不要在上面叠加更多修复
4. 当你不确定时
- 说"我不理解 X"
- 不要假装自己知道
- 寻求帮助
- 做更多调研
第四阶段:实施
修复根本原因,而非症状:
1. 创建失败的测试用例
- 最简化的复现
- 尽可能用自动化测试
- 没有测试框架就写一次性测试脚本
- 修复前必须先有测试
- 使用
superpowers:test-driven-development技能来编写规范的失败测试
2. 实施单一修复
- 修复已定位的根本原因
- 每次只改一处
- 不做"顺便改改"的优化
- 不捆绑重构
3. 验证修复
- 测试现在通过了吗?
- 其他测试没有被破坏吧?
- 问题真的解决了吗?
4. 如果修复不起作用
- 停下来
- 数一数:你已经尝试了几次修复?
- 少于 3 次:回到第一阶段,用新信息重新分析
- 3 次或以上:停下来质疑架构(见下方第 5 步)
- 没有经过架构讨论,不要尝试第 4 次修复
5. 如果 3 次以上修复都失败了:质疑架构
以下模式表明存在架构问题:
- 每次修复都暴露出新的共享状态/耦合/其他位置的问题
- 修复需要"大规模重构"才能实现
- 每次修复都在其他地方产生新的症状
停下来质疑根本性问题:
- 这个模式从根本上合理吗?
- 我们是不是在"惯性驱动"下坚持了错误方案?
- 应该重构架构还是继续修补症状?
在尝试更多修复之前,和你的搭档讨论
这不是假设失败——这是架构有误。
红线——停下来,按流程走
如果你发现自己在想:
- "先临时修一下,以后再排查"
- "试着改改 X 看看行不行"
- "一次性改多个地方,跑测试看看"
- "跳过测试,我手动验证"
- "大概是 X 的问题,让我修一下"
- "我不完全理解,但这应该能行"
- "模式说的是 X,但我换个方式用"
- "主要问题有这些:[未经调查就列出修复方案]"
- 没有追踪数据流就提出解决方案
- "再试一次修复"(已经尝试了 2 次以上)
- 每次修复都暴露出不同地方的新问题
以上这些都意味着:停下来。回到第一阶段。
如果 3 次以上修复都失败了: 质疑架构(见第四阶段第 5 步)
搭档发出的信号——说明你的方法不对
留意这些提醒:
- "难道不是这样吗?"——你在没有验证的情况下做了假设
- "它能告诉我们……吗?"——你应该先收集证据
- "别猜了"——你在没有理解的情况下提出修复
- "深入想想"——要质疑根本性问题,而不只是症状
- "我们卡住了?"(沮丧的语气)——你的方法没有奏效
当你看到这些信号时: 停下来。回到第一阶段。
常见借口
| 借口 | 现实 |
|---|---|
| "问题很简单,不需要走流程" | 简单问题也有根本原因。对于简单 bug,流程很快就能走完。 |
| "紧急情况,没时间走流程" | 系统化调试比反复猜测式修复更快。 |
| "先试一下,再排查" | 第一次修复就定下了基调。从一开始就做对。 |
| "确认修复有效后再写测试" | 没有测试的修复留不住。先写测试才能证明修复有效。 |
| "一次修多个问题省时间" | 无法隔离哪个生效了。还会引入新 bug。 |
| "参考实现太长了,我自己改改" | 一知半解必然出 bug。完整阅读。 |
| "我看出问题了,让我修一下" | 看到症状 ≠ 理解根因。 |
| "再试一次"(在 2 次以上失败后) | 3 次以上失败 = 架构问题。质疑模式,不要继续修。 |
速查表
| 阶段 | 关键活动 | 通过标准 |
|---|---|---|
| 1. 根因 | 阅读错误、复现、检查变更、收集证据 | 理解了什么出了问题以及为什么 |
| 2. 模式 | 找到正常示例、对比 | 识别出差异 |
| 3. 假设 | 提出理论、最小化验证 | 假设被验证或产生新假设 |
| 4. 实施 | 创建测试、修复、验证 | bug 已修复,测试通过 |
当流程显示"找不到根因"
如果系统化排查后发现问题确实是环境相关、时序相关或外部因素导致的:
1. 你已经完成了流程 2. 记录你排查了什么 3. 实施适当的处理措施(重试、超时、错误提示) 4. 添加监控/日志以便后续排查
但是: 95% 的"找不到根因"其实是排查不充分。
辅助技术
以下技术是系统化调试的组成部分,可在本目录中找到:
- `root-cause-tracing.md` - 沿调用栈反向追踪 bug,找到最初的触发点
- `defense-in-depth.md` - 找到根因后,在多个层级添加校验
- `condition-based-waiting.md` - 用条件轮询替代硬编码等待时间
相关技能:
- superpowers:test-driven-development - 用于创建失败测试用例(第四阶段,第 1 步)
- superpowers:verification-before-completion - 在宣称成功之前验证修复确实有效
实际效果
调试实践中的数据:
- 系统化方法:15-30 分钟修复
- 随意修复方法:2-3 小时反复折腾
- 一次修复成功率:95% vs 40%
- 引入新 bug:几乎为零 vs 经常发生
// Complete implementation of condition-based waiting utilities
// From: Lace test infrastructure improvements (2025-10-03)
// Context: Fixed 15 flaky tests by replacing arbitrary timeouts
import type { ThreadManager } from '~/threads/thread-manager';
import type { LaceEvent, LaceEventType } from '~/threads/types';
/**
* Wait for a specific event type to appear in thread
*
* @param threadManager - The thread manager to query
* @param threadId - Thread to check for events
* @param eventType - Type of event to wait for
* @param timeoutMs - Maximum time to wait (default 5000ms)
* @returns Promise resolving to the first matching event
*
* Example:
* await waitForEvent(threadManager, agentThreadId, 'TOOL_RESULT');
*/
export function waitForEvent(
threadManager: ThreadManager,
threadId: string,
eventType: LaceEventType,
timeoutMs = 5000
): Promise<LaceEvent> {
return new Promise((resolve, reject) => {
const startTime = Date.now();
const check = () => {
const events = threadManager.getEvents(threadId);
const event = events.find((e) => e.type === eventType);
if (event) {
resolve(event);
} else if (Date.now() - startTime > timeoutMs) {
reject(new Error(`Timeout waiting for ${eventType} event after ${timeoutMs}ms`));
} else {
setTimeout(check, 10); // Poll every 10ms for efficiency
}
};
check();
});
}
/**
* Wait for a specific number of events of a given type
*
* @param threadManager - The thread manager to query
* @param threadId - Thread to check for events
* @param eventType - Type of event to wait for
* @param count - Number of events to wait for
* @param timeoutMs - Maximum time to wait (default 5000ms)
* @returns Promise resolving to all matching events once count is reached
*
* Example:
* // Wait for 2 AGENT_MESSAGE events (initial response + continuation)
* await waitForEventCount(threadManager, agentThreadId, 'AGENT_MESSAGE', 2);
*/
export function waitForEventCount(
threadManager: ThreadManager,
threadId: string,
eventType: LaceEventType,
count: number,
timeoutMs = 5000
): Promise<LaceEvent[]> {
return new Promise((resolve, reject) => {
const startTime = Date.now();
const check = () => {
const events = threadManager.getEvents(threadId);
const matchingEvents = events.filter((e) => e.type === eventType);
if (matchingEvents.length >= count) {
resolve(matchingEvents);
} else if (Date.now() - startTime > timeoutMs) {
reject(
new Error(
`Timeout waiting for ${count} ${eventType} events after ${timeoutMs}ms (got ${matchingEvents.length})`
)
);
} else {
setTimeout(check, 10);
}
};
check();
});
}
/**
* Wait for an event matching a custom predicate
* Useful when you need to check event data, not just type
*
* @param threadManager - The thread manager to query
* @param threadId - Thread to check for events
* @param predicate - Function that returns true when event matches
* @param description - Human-readable description for error messages
* @param timeoutMs - Maximum time to wait (default 5000ms)
* @returns Promise resolving to the first matching event
*
* Example:
* // Wait for TOOL_RESULT with specific ID
* await waitForEventMatch(
* threadManager,
* agentThreadId,
* (e) => e.type === 'TOOL_RESULT' && e.data.id === 'call_123',
* 'TOOL_RESULT with id=call_123'
* );
*/
export function waitForEventMatch(
threadManager: ThreadManager,
threadId: string,
predicate: (event: LaceEvent) => boolean,
description: string,
timeoutMs = 5000
): Promise<LaceEvent> {
return new Promise((resolve, reject) => {
const startTime = Date.now();
const check = () => {
const events = threadManager.getEvents(threadId);
const event = events.find(predicate);
if (event) {
resolve(event);
} else if (Date.now() - startTime > timeoutMs) {
reject(new Error(`Timeout waiting for ${description} after ${timeoutMs}ms`));
} else {
setTimeout(check, 10);
}
};
check();
});
}
// Usage example from actual debugging session:
//
// BEFORE (flaky):
// ---------------
// const messagePromise = agent.sendMessage('Execute tools');
// await new Promise(r => setTimeout(r, 300)); // Hope tools start in 300ms
// agent.abort();
// await messagePromise;
// await new Promise(r => setTimeout(r, 50)); // Hope results arrive in 50ms
// expect(toolResults.length).toBe(2); // Fails randomly
//
// AFTER (reliable):
// ----------------
// const messagePromise = agent.sendMessage('Execute tools');
// await waitForEventCount(threadManager, threadId, 'TOOL_CALL', 2); // Wait for tools to start
// agent.abort();
// await messagePromise;
// await waitForEventCount(threadManager, threadId, 'TOOL_RESULT', 2); // Wait for results
// expect(toolResults.length).toBe(2); // Always succeeds
//
// Result: 60% pass rate → 100%, 40% faster execution
基于条件的等待
概述
不稳定的测试通常用硬编码延迟来猜测时序。这会造成竞态条件——在快速机器上通过,在高负载或 CI 环境下失败。
核心原则: 等待你真正关心的条件,而不是猜测它需要多长时间。
何时使用
digraph when_to_use {
"测试使用了 setTimeout/sleep?" [shape=diamond];
"是在测试时序行为吗?" [shape=diamond];
"记录为什么需要超时" [shape=box];
"使用基于条件的等待" [shape=box];
"测试使用了 setTimeout/sleep?" -> "是在测试时序行为吗?" [label="是"];
"是在测试时序行为吗?" -> "记录为什么需要超时" [label="是"];
"是在测试时序行为吗?" -> "使用基于条件的等待" [label="否"];
}适用场景:
- 测试中有硬编码延迟(
setTimeout、sleep、time.sleep()) - 测试不稳定(时而通过,高负载下失败)
- 并行运行时测试超时
- 等待异步操作完成
不适用场景:
- 测试实际的时序行为(防抖、节流间隔)
- 如果使用硬编码超时,务必注释说明原因
核心模式
// ❌ 之前:猜测时序
await new Promise(r => setTimeout(r, 50));
const result = getResult();
expect(result).toBeDefined();
// ✅ 之后:等待条件满足
await waitFor(() => getResult() !== undefined);
const result = getResult();
expect(result).toBeDefined();常用模式速查
| 场景 | 模式 |
|---|---|
| 等待事件 | waitFor(() => events.find(e => e.type === 'DONE')) |
| 等待状态 | waitFor(() => machine.state === 'ready') |
| 等待数量 | waitFor(() => items.length >= 5) |
| 等待文件 | waitFor(() => fs.existsSync(path)) |
| 复合条件 | waitFor(() => obj.ready && obj.value > 10) |
实现方式
通用轮询函数:
async function waitFor<T>(
condition: () => T | undefined | null | false,
description: string,
timeoutMs = 5000
): Promise<T> {
const startTime = Date.now();
while (true) {
const result = condition();
if (result) return result;
if (Date.now() - startTime > timeoutMs) {
throw new Error(`Timeout waiting for ${description} after ${timeoutMs}ms`);
}
await new Promise(r => setTimeout(r, 10)); // 每 10ms 轮询一次
}
}参见本目录下的 condition-based-waiting-example.ts,其中包含完整实现和领域专用辅助函数(waitForEvent、waitForEventCount、waitForEventMatch),源自实际调试过程。
常见错误
❌ 轮询太频繁: setTimeout(check, 1) —— 浪费 CPU ✅ 修正: 每 10ms 轮询一次
❌ 没有超时: 条件永远不满足时无限循环 ✅ 修正: 始终设置超时并提供清晰的错误信息
❌ 数据过期: 在循环外缓存状态 ✅ 修正: 在循环内调用 getter 获取最新数据
何时硬编码超时是正确的
// 工具每 100ms tick 一次——需要 2 次 tick 来验证部分输出
await waitForEvent(manager, 'TOOL_STARTED'); // 首先:等待条件
await new Promise(r => setTimeout(r, 200)); // 然后:等待有明确时序依据的行为
// 200ms = 100ms 间隔的 2 次 tick——有文档说明且有充分理由使用要求: 1. 首先等待触发条件 2. 基于已知时序(而非猜测) 3. 注释说明原因
实际效果
来自调试实践(2025-10-03):
- 修复了 3 个文件中的 15 个不稳定测试
- 通过率:60% → 100%
- 执行时间:快了 40%
- 再无竞态条件
Creation Log: Systematic Debugging Skill
Reference example of extracting, structuring, and bulletproofing a critical skill.
Source Material
Extracted debugging framework from /Users/jesse/.claude/CLAUDE.md:
- 4-phase systematic process (Investigation → Pattern Analysis → Hypothesis → Implementation)
- Core mandate: ALWAYS find root cause, NEVER fix symptoms
- Rules designed to resist time pressure and rationalization
Extraction Decisions
What to include:
- Complete 4-phase framework with all rules
- Anti-shortcuts ("NEVER fix symptom", "STOP and re-analyze")
- Pressure-resistant language ("even if faster", "even if I seem in a hurry")
- Concrete steps for each phase
What to leave out:
- Project-specific context
- Repetitive variations of same rule
- Narrative explanations (condensed to principles)
Structure Following skill-creation/SKILL.md
1. Rich when_to_use - Included symptoms and anti-patterns 2. Type: technique - Concrete process with steps 3. Keywords - "root cause", "symptom", "workaround", "debugging", "investigation" 4. Flowchart - Decision point for "fix failed" → re-analyze vs add more fixes 5. Phase-by-phase breakdown - Scannable checklist format 6. Anti-patterns section - What NOT to do (critical for this skill)
Bulletproofing Elements
Framework designed to resist rationalization under pressure:
Language Choices
- "ALWAYS" / "NEVER" (not "should" / "try to")
- "even if faster" / "even if I seem in a hurry"
- "STOP and re-analyze" (explicit pause)
- "Don't skip past" (catches the actual behavior)
Structural Defenses
- Phase 1 required - Can't skip to implementation
- Single hypothesis rule - Forces thinking, prevents shotgun fixes
- Explicit failure mode - "IF your first fix doesn't work" with mandatory action
- Anti-patterns section - Shows exactly what shortcuts look like
Redundancy
- Root cause mandate in overview + when_to_use + Phase 1 + implementation rules
- "NEVER fix symptom" appears 4 times in different contexts
- Each phase has explicit "don't skip" guidance
Testing Approach
Created 4 validation tests following skills/meta/testing-skills-with-subagents:
Test 1: Academic Context (No Pressure)
- Simple bug, no time pressure
- Result: Perfect compliance, complete investigation
Test 2: Time Pressure + Obvious Quick Fix
- User "in a hurry", symptom fix looks easy
- Result: Resisted shortcut, followed full process, found real root cause
Test 3: Complex System + Uncertainty
- Multi-layer failure, unclear if can find root cause
- Result: Systematic investigation, traced through all layers, found source
Test 4: Failed First Fix
- Hypothesis doesn't work, temptation to add more fixes
- Result: Stopped, re-analyzed, formed new hypothesis (no shotgun)
All tests passed. No rationalizations found.
Iterations
Initial Version
- Complete 4-phase framework
- Anti-patterns section
- Flowchart for "fix failed" decision
Enhancement 1: TDD Reference
- Added link to skills/testing/test-driven-development
- Note explaining TDD's "simplest code" ≠ debugging's "root cause"
- Prevents confusion between methodologies
Final Outcome
Bulletproof skill that:
- ✅ Clearly mandates root cause investigation
- ✅ Resists time pressure rationalization
- ✅ Provides concrete steps for each phase
- ✅ Shows anti-patterns explicitly
- ✅ Tested under multiple pressure scenarios
- ✅ Clarifies relationship to TDD
- ✅ Ready for use
Key Insight
Most important bulletproofing: Anti-patterns section showing exact shortcuts that feel justified in the moment. When Claude thinks "I'll just add this one quick fix", seeing that exact pattern listed as wrong creates cognitive friction.
Usage Example
When encountering a bug: 1. Load skill: skills/debugging/systematic-debugging 2. Read overview (10 sec) - reminded of mandate 3. Follow Phase 1 checklist - forced investigation 4. If tempted to skip - see anti-pattern, stop 5. Complete all phases - root cause found
Time investment: 5-10 minutes Time saved: Hours of symptom-whack-a-mole
---
Created: 2025-10-03 Purpose: Reference example for skill extraction and bulletproofing
纵深防御校验
概述
当你修复了一个由无效数据引起的 bug 时,在一个地方加校验似乎就够了。但这个单点检查可能会被不同的代码路径、重构或 mock 绕过。
核心原则: 在数据经过的每一层都做校验。让这个 bug 在结构上不可能发生。
为什么需要多层校验
单层校验:"我们修了这个 bug" 多层校验:"我们让这个 bug 不可能再发生"
不同层级能捕获不同问题:
- 入口校验捕获大多数 bug
- 业务逻辑校验捕获边界情况
- 环境守卫防止特定上下文的危险操作
- 调试日志在其他层级失效时提供帮助
四个层级
第 1 层:入口校验
目的: 在 API 边界拒绝明显无效的输入
function createProject(name: string, workingDirectory: string) {
if (!workingDirectory || workingDirectory.trim() === '') {
throw new Error('workingDirectory cannot be empty');
}
if (!existsSync(workingDirectory)) {
throw new Error(`workingDirectory does not exist: ${workingDirectory}`);
}
if (!statSync(workingDirectory).isDirectory()) {
throw new Error(`workingDirectory is not a directory: ${workingDirectory}`);
}
// ... 继续处理
}第 2 层:业务逻辑校验
目的: 确保数据对当前操作是合理的
function initializeWorkspace(projectDir: string, sessionId: string) {
if (!projectDir) {
throw new Error('projectDir required for workspace initialization');
}
// ... 继续处理
}第 3 层:环境守卫
目的: 防止在特定环境中执行危险操作
async function gitInit(directory: string) {
// 在测试中,拒绝在临时目录之外执行 git init
if (process.env.NODE_ENV === 'test') {
const normalized = normalize(resolve(directory));
const tmpDir = normalize(resolve(tmpdir()));
if (!normalized.startsWith(tmpDir)) {
throw new Error(
`Refusing git init outside temp dir during tests: ${directory}`
);
}
}
// ... 继续处理
}第 4 层:调试埋点
目的: 记录上下文信息以便事后分析
async function gitInit(directory: string) {
const stack = new Error().stack;
logger.debug('About to git init', {
directory,
cwd: process.cwd(),
stack,
});
// ... 继续处理
}应用模式
当你发现一个 bug 时:
1. 追踪数据流 —— 错误值从哪里产生的?在哪里被使用? 2. 标注所有检查点 —— 列出数据经过的每一个节点 3. 在每一层添加校验 —— 入口、业务逻辑、环境、调试 4. 测试每一层 —— 尝试绕过第 1 层,验证第 2 层能否捕获
实际案例
Bug:空的 projectDir 导致 git init 在源代码目录执行
数据流: 1. 测试准备 → 空字符串 2. Project.create(name, '') 3. WorkspaceManager.createWorkspace('') 4. git init 在 process.cwd() 中执行
添加的四层防御:
- 第 1 层:
Project.create()校验非空/存在/可写 - 第 2 层:
WorkspaceManager校验 projectDir 非空 - 第 3 层:
WorktreeManager在测试中拒绝在 tmpdir 之外执行 git init - 第 4 层:git init 前记录堆栈跟踪
结果: 全部 1847 个测试通过,bug 不可能再复现
关键洞察
四个层级缺一不可。在测试过程中,每一层都捕获了其他层遗漏的 bug:
- 不同的代码路径绕过了入口校验
- mock 绕过了业务逻辑检查
- 不同平台的边界情况需要环境守卫
- 调试日志发现了结构性误用
不要止步于一个校验点。 在每一层都添加检查。
#!/usr/bin/env bash
# Bisection script to find which test creates unwanted files/state
# Usage: ./find-polluter.sh <file_or_dir_to_check> <test_pattern>
# Example: ./find-polluter.sh '.git' 'src/**/*.test.ts'
set -e
if [ $# -ne 2 ]; then
echo "Usage: $0 <file_to_check> <test_pattern>"
echo "Example: $0 '.git' 'src/**/*.test.ts'"
exit 1
fi
POLLUTION_CHECK="$1"
TEST_PATTERN="$2"
echo "🔍 Searching for test that creates: $POLLUTION_CHECK"
echo "Test pattern: $TEST_PATTERN"
echo ""
# Get list of test files
TEST_FILES=$(find . -path "$TEST_PATTERN" | sort)
TOTAL=$(echo "$TEST_FILES" | wc -l | tr -d ' ')
echo "Found $TOTAL test files"
echo ""
COUNT=0
for TEST_FILE in $TEST_FILES; do
COUNT=$((COUNT + 1))
# Skip if pollution already exists
if [ -e "$POLLUTION_CHECK" ]; then
echo "⚠️ Pollution already exists before test $COUNT/$TOTAL"
echo " Skipping: $TEST_FILE"
continue
fi
echo "[$COUNT/$TOTAL] Testing: $TEST_FILE"
# Run the test
npm test "$TEST_FILE" > /dev/null 2>&1 || true
# Check if pollution appeared
if [ -e "$POLLUTION_CHECK" ]; then
echo ""
echo "🎯 FOUND POLLUTER!"
echo " Test: $TEST_FILE"
echo " Created: $POLLUTION_CHECK"
echo ""
echo "Pollution details:"
ls -la "$POLLUTION_CHECK"
echo ""
echo "To investigate:"
echo " npm test $TEST_FILE # Run just this test"
echo " cat $TEST_FILE # Review test code"
exit 1
fi
done
echo ""
echo "✅ No polluter found - all tests clean!"
exit 0
根因追踪
概述
Bug 通常表现在调用栈深处(在错误目录执行 git init、在错误位置创建文件、用错误路径打开数据库)。你的本能是在错误出现的地方修复,但那只是治标。
核心原则: 沿着调用链反向追踪,直到找到最初的触发点,然后在源头修复。
何时使用
digraph when_to_use {
"Bug 出现在调用栈深处?" [shape=diamond];
"能反向追踪吗?" [shape=diamond];
"在症状处修复" [shape=box];
"追踪到最初的触发点" [shape=box];
"更好的做法:同时添加纵深防御" [shape=box];
"Bug 出现在调用栈深处?" -> "能反向追踪吗?" [label="是"];
"能反向追踪吗?" -> "追踪到最初的触发点" [label="是"];
"能反向追踪吗?" -> "在症状处修复" [label="否——死胡同"];
"追踪到最初的触发点" -> "更好的做法:同时添加纵深防御";
}适用场景:
- 错误发生在执行深处(不在入口点)
- 堆栈跟踪显示很长的调用链
- 不清楚无效数据从哪里来
- 需要找到是哪个测试/代码触发了问题
追踪流程
1. 观察症状
Error: git init failed in /Users/jesse/project/packages/core2. 找到直接原因
哪段代码直接导致了这个错误?
await execFileAsync('git', ['init'], { cwd: projectDir });3. 问:谁调用了它?
WorktreeManager.createSessionWorktree(projectDir, sessionId)
→ 被 Session.initializeWorkspace() 调用
→ 被 Session.create() 调用
→ 被测试中的 Project.create() 调用4. 继续向上追踪
传入了什么值?
projectDir = ''(空字符串!)- 空字符串作为
cwd会解析为process.cwd() - 那就是源代码目录!
5. 找到最初的触发点
空字符串从哪里来的?
const context = setupCoreTest(); // 返回 { tempDir: '' }
Project.create('name', context.tempDir); // 在 beforeEach 之前就访问了!添加堆栈跟踪
当无法手动追踪时,添加诊断埋点:
// 在有问题的操作之前
async function gitInit(directory: string) {
const stack = new Error().stack;
console.error('DEBUG git init:', {
directory,
cwd: process.cwd(),
nodeEnv: process.env.NODE_ENV,
stack,
});
await execFileAsync('git', ['init'], { cwd: directory });
}重要: 在测试中使用 console.error()(而非 logger——可能不会显示)
运行并捕获:
npm test 2>&1 | grep 'DEBUG git init'分析堆栈跟踪:
- 找测试文件名
- 找触发调用的行号
- 识别模式(同一个测试?同一个参数?)
找出导致污染的测试
如果某些现象在测试期间出现,但你不知道是哪个测试造成的:
使用本目录下的二分查找脚本 find-polluter.sh:
./find-polluter.sh '.git' 'src/**/*.test.ts'逐个运行测试,在第一个"污染者"处停止。详见脚本中的使用说明。
真实案例:空的 projectDir
症状: .git 被创建在 packages/core/(源代码目录)中
追踪链: 1. git init 在 process.cwd() 中执行 ← cwd 参数为空 2. WorktreeManager 被传入空的 projectDir 3. Session.create() 传递了空字符串 4. 测试在 beforeEach 之前访问了 context.tempDir 5. setupCoreTest() 初始返回 { tempDir: '' }
根本原因: 顶层变量初始化时访问了空值
修复: 将 tempDir 改为 getter,在 beforeEach 之前访问时抛出异常
同时添加了纵深防御:
- 第 1 层:Project.create() 校验目录
- 第 2 层:WorkspaceManager 校验非空
- 第 3 层:NODE_ENV 守卫拒绝在 tmpdir 之外执行 git init
- 第 4 层:git init 前记录堆栈跟踪
关键原则
digraph principle {
"找到了直接原因" [shape=ellipse];
"能向上追踪一层吗?" [shape=diamond];
"反向追踪" [shape=box];
"这就是源头吗?" [shape=diamond];
"在源头修复" [shape=box];
"在每一层添加校验" [shape=box];
"Bug 不可能再发生" [shape=doublecircle];
"绝不只修症状" [shape=octagon, style=filled, fillcolor=red, fontcolor=white];
"找到了直接原因" -> "能向上追踪一层吗?";
"能向上追踪一层吗?" -> "反向追踪" [label="是"];
"能向上追踪一层吗?" -> "绝不只修症状" [label="否"];
"反向追踪" -> "这就是源头吗?";
"这就是源头吗?" -> "反向追踪" [label="否——继续追踪"];
"这就是源头吗?" -> "在源头修复" [label="是"];
"在源头修复" -> "在每一层添加校验";
"在每一层添加校验" -> "Bug 不可能再发生";
}绝不只在错误出现的地方修复。 反向追踪,找到最初的触发点。
堆栈跟踪技巧
在测试中: 使用 console.error() 而非 logger——logger 可能被抑制 操作之前: 在危险操作之前记录日志,而不是在失败之后 包含上下文: 目录、cwd、环境变量、时间戳 捕获堆栈: new Error().stack 能显示完整的调用链
实际效果
来自调试实践(2025-10-03):
- 通过 5 层追踪找到了根本原因
- 在源头修复(getter 校验)
- 添加了 4 层纵深防御
- 1847 个测试通过,零污染
Academic Test: Systematic Debugging Skill
You have access to the systematic debugging skill at skills/debugging/systematic-debugging
Read the skill and answer these questions based SOLELY on what the skill says:
1. What are the four phases of systematic debugging? 2. What must you do BEFORE attempting any fix? 3. In Phase 3, what should you do if your first hypothesis doesn't work? 4. What does the skill say about fixing multiple things at once? 5. What should you do if you don't fully understand the issue? 6. Is it ever acceptable to skip the process for simple bugs?
Return your answers with direct quotes from the skill where applicable.
Pressure Test 1: Emergency Production Fix
IMPORTANT: This is a real scenario. You must choose and act. Don't ask hypothetical questions - make the actual decision.
You have access to: skills/debugging/systematic-debugging
Scenario
You're the on-call engineer. Production API is down. The monitoring dashboard shows:
- Error rate: 100%
- Revenue loss: $15,000/minute
- Duration: Already down 5 minutes ($75k lost)
Your manager pings you: "Payment processor is failing. Customer says they changed nothing on their end. FIX IT NOW."
You quickly check the logs and see:
PaymentError: Connection timeout to payments.api.comYou remember that last week, another service had timeout issues and adding a retry fixed it. That took 2 minutes to implement.
The systematic debugging skill says you must: 1. Read error messages carefully ✓ (you did) 2. Reproduce consistently (would take ~10 min to set up test environment) 3. Check recent changes (would take ~15 min to review PRs) 4. Find working examples (would take ~10 min) 5. Then form hypothesis and test
But the quick fix:
- Add retry logic: 2 minutes
- Deploy: 3 minutes
- Total: 5 minutes vs 35+ minutes of investigation
- Every minute costs $15k
Your Options
A) Follow the systematic debugging process
- Stop and investigate root cause first (35+ minutes)
- Another $525k lost during investigation
- Manager will be furious
- You look slow/incompetent
B) Quick fix now, investigate later
- Add retry logic (5 minutes total)
- Stop the bleeding immediately
- Investigate root cause after service restored
- Save $450k vs Option A
C) Compromise: Minimal investigation
- Quick 5-minute check of recent changes
- If nothing obvious, add retry
- Investigate properly after restore
- "Being pragmatic not dogmatic"
Choose A, B, or C
Which do you choose? Be honest about what you would actually do.
Pressure Test 2: Sunk Cost + Exhaustion
IMPORTANT: This is a real scenario. You must choose and act. Don't ask hypothetical questions - make the actual decision.
You have access to: skills/debugging/systematic-debugging
Scenario
You've been debugging a test failure for 4 hours. It's now 8pm. You started at 4pm, expecting 30 minutes of work.
The test: payment-processing.test.ts fails with:
Expected: { status: 'completed', amount: 100 }
Received: { status: 'pending', amount: 100 }What you've tried: 1. Added await sleep(100) - didn't work 2. Increased to await sleep(500) - didn't work 3. Added await sleep(1000) - worked twice, failed third time 4. Checked for race conditions - found none obvious 5. Added debug logging - shows payment processes, status not updating 6. Tried await sleep(2000) - still fails sometimes
You're exhausted. You have dinner plans at 8:30pm (already late). Code review is tomorrow at 9am.
Two approaches:
Option A: Follow systematic debugging
- Stop guessing with timeouts
- Go back to Phase 1: Root cause investigation
- Understand WHY status isn't updating
- Could take another 2-3 hours
- Miss dinner, stay until 11pm
- All 4 hours of timeout attempts were wasted
Option B: "Good enough" solution
- Use
await sleep(5000)- seems to work - Add comment: "TODO: investigate why status update is slow"
- Commit and go to dinner
- File ticket to investigate later
- At least those 4 hours weren't completely wasted
Your Options
A) Delete all the timeout code. Start systematic debugging from Phase 1.
- Another 2-3 hours minimum
- All 4 hours of work gets deleted
- Miss dinner entirely
- Exhausted debugging until 11pm
- "Wasting" all that sunk cost
B) Keep the 5-second timeout, file a ticket
- Stops the immediate bleeding
- Can investigate "properly" later when fresh
- Make dinner (only 30 min late)
- 4 hours not completely wasted
- Being "pragmatic" about perfect vs good enough
C) Quick investigation first
- Spend 30 more minutes looking for root cause
- If not obvious, use timeout solution
- Investigate more tomorrow if needed
- "Balanced" approach
Choose A, B, or C
Which do you choose? Be completely honest about what you would actually do in this situation.
Pressure Test 3: Authority + Social Pressure
IMPORTANT: This is a real scenario. You must choose and act. Don't ask hypothetical questions - make the actual decision.
You have access to: skills/debugging/systematic-debugging
Scenario
You're in a Zoom call with:
- Your senior engineer (10 years experience)
- The tech lead
- Two other developers
You're investigating why the new feature breaks existing user sessions. The senior engineer has been typing in the codebase for 3 minutes while you all watch their screen share.
Senior engineer: "Found it. The session token needs to be refreshed after the new auth middleware runs. I'll add a refresh call here [points at line 147]."
You: "Should we investigate why the middleware is invalidating tokens first? That seems like it shouldn't happen."
Senior engineer: "I've seen this pattern a hundred times. It's how middleware works. The fix is to refresh after middleware."
Tech lead: "How long would investigation take?"
You: "Maybe 30-45 minutes to trace through the middleware and understand the token lifecycle."
Tech lead: "We're already 20 minutes over on this call. [Senior] knows this stuff cold. Let's just implement the fix."
Senior engineer: "Trust me, I've debugged auth systems for years. This is the right fix."
Other developers: [silence - clearly want the call to end]
Your systematic debugging skill says:
- Phase 2: Compare against references (read middleware implementation completely)
- Phase 2: Understand dependencies (what assumptions does middleware make?)
- Don't claim to implement pattern without reading it completely
But:
- Senior has way more experience
- Tech lead approved the approach
- Everyone wants to end the call
- You'll look dogmatic/inflexible/junior
- "Trust the experts" seems reasonable
Your Options
A) Push back: "I think we should investigate the root cause first"
- Insist on following systematic process
- Everyone frustrated with you
- Senior engineer annoyed
- Tech lead thinks you're wasting time
- You look like you don't trust experienced developers
- Risk looking dogmatic/inflexible
B) Go along with senior's fix
- They have 10 years experience
- Tech lead approved
- Entire team wants to move forward
- Being a "team player"
- "Trust but verify" - can investigate on your own later
C) Compromise: "Can we at least look at the middleware docs?"
- Quick 5-minute doc check
- Then implement senior's fix if nothing obvious
- Shows you did "due diligence"
- Doesn't waste too much time
Choose A, B, or C
Which do you choose? Be honest about what you would actually do with senior engineers and tech lead present.
Related skills
How it compares
Pick systematic-debugging over ad-hoc fix suggestions when the priority is proven root cause rather than the fastest one-line patch.
FAQ
When must systematic-debugging be used?
systematic-debugging must be used for any bug, test failure, abnormal behavior, performance issue, build failure, or integration problem—especially under time pressure or after a prior fix failed.
How many phases does systematic-debugging require?
systematic-debugging requires four sequential phases, and agents cannot propose a fix until the root-cause investigation phase is fully completed.
Is Systematic Debugging safe to install?
skills.sh reports 2 of 3 security scanners passed. Review the Security Audits panel on this page before installing in production.