跳到主要内容

系统化调试

收藏0

遇到 bug、测试失败或异常行为时,先定位根因再修复的四阶段调试流程

让 AI 安装

wencker最新版本 0.1.0更新于 2026-11-05下载 0 次

root-cause-tracing.md

5.2 KB查看排版效果
1# Root Cause Tracing
2
3## Overview
4
5Bugs often manifest deep in the call stack (git init in wrong directory, file created in wrong location, database opened with wrong path). Your instinct is to fix where the error appears, but that's treating a symptom.
6
7**Core principle:** Trace backward through the call chain until you find the original trigger, then fix at the source.
8
9## When to Use
10
11```dot
12digraph when_to_use {
13 "Bug appears deep in stack?" [shape=diamond];
14 "Can trace backwards?" [shape=diamond];
15 "Fix at symptom point" [shape=box];
16 "Trace to original trigger" [shape=box];
17 "BETTER: Also add defense-in-depth" [shape=box];
18
19 "Bug appears deep in stack?" -> "Can trace backwards?" [label="yes"];
20 "Can trace backwards?" -> "Trace to original trigger" [label="yes"];
21 "Can trace backwards?" -> "Fix at symptom point" [label="no - dead end"];
22 "Trace to original trigger" -> "BETTER: Also add defense-in-depth";
23}
24```
25
26**Use when:**
27- Error happens deep in execution (not at entry point)
28- Stack trace shows long call chain
29- Unclear where invalid data originated
30- Need to find which test/code triggers the problem
31
32## The Tracing Process
33
34### 1. Observe the Symptom
35```
36Error: git init failed in ~/project/packages/core
37```
38
39### 2. Find Immediate Cause
40**What code directly causes this?**
41```typescript
42await execFileAsync('git', ['init'], { cwd: projectDir });
43```
44
45### 3. Ask: What Called This?
46```typescript
47WorktreeManager.createSessionWorktree(projectDir, sessionId)
48 → called by Session.initializeWorkspace()
49 → called by Session.create()
50 → called by test at Project.create()
51```
52
53### 4. Keep Tracing Up
54**What value was passed?**
55- `projectDir = ''` (empty string!)
56- Empty string as `cwd` resolves to `process.cwd()`
57- That's the source code directory!
58
59### 5. Find Original Trigger
60**Where did empty string come from?**
61```typescript
62const context = setupCoreTest(); // Returns { tempDir: '' }
63Project.create('name', context.tempDir); // Accessed before beforeEach!
64```
65
66## Adding Stack Traces
67
68When you can't trace manually, add instrumentation:
69
70```typescript
71// Before the problematic operation
72async function gitInit(directory: string) {
73 const stack = new Error().stack;
74 console.error('DEBUG git init:', {
75 directory,
76 cwd: process.cwd(),
77 nodeEnv: process.env.NODE_ENV,
78 stack,
79 });
80
81 await execFileAsync('git', ['init'], { cwd: directory });
82}
83```
84
85**Critical:** Use `console.error()` in tests (not logger - may not show)
86
87**Run and capture:**
88```bash
89npm test 2>&1 | grep 'DEBUG git init'
90```
91
92**Analyze stack traces:**
93- Look for test file names
94- Find the line number triggering the call
95- Identify the pattern (same test? same parameter?)
96
97## Finding Which Test Causes Pollution
98
99If something appears during tests but you don't know which test:
100
101Use the bisection script `find-polluter.sh` in this directory:
102
103```bash
104bash ./find-polluter.sh '.git' 'src/**/*.test.ts'
105```
106
107Runs tests one-by-one, stops at first polluter. See script for usage.
108
109## Real Example: Empty projectDir
110
111**Symptom:** `.git` created in `packages/core/` (source code)
112
113**Trace chain:**
1141. `git init` runs in `process.cwd()` ← empty cwd parameter
1152. WorktreeManager called with empty projectDir
1163. Session.create() passed empty string
1174. Test accessed `context.tempDir` before beforeEach
1185. setupCoreTest() returns `{ tempDir: '' }` initially
119
120**Root cause:** Top-level variable initialization accessing empty value
121
122**Fix:** Made tempDir a getter that throws if accessed before beforeEach
123
124**Also added defense-in-depth:**
125- Layer 1: Project.create() validates directory
126- Layer 2: WorkspaceManager validates not empty
127- Layer 3: NODE_ENV guard refuses git init outside tmpdir
128- Layer 4: Stack trace logging before git init
129
130## Key Principle
131
132```dot
133digraph principle {
134 "Found immediate cause" [shape=ellipse];
135 "Can trace one level up?" [shape=diamond];
136 "Trace backwards" [shape=box];
137 "Is this the source?" [shape=diamond];
138 "Fix at source" [shape=box];
139 "Add validation at each layer" [shape=box];
140 "Bug impossible" [shape=doublecircle];
141 "NEVER fix just the symptom" [shape=octagon, style=filled, fillcolor=red, fontcolor=white];
142
143 "Found immediate cause" -> "Can trace one level up?";
144 "Can trace one level up?" -> "Trace backwards" [label="yes"];
145 "Can trace one level up?" -> "NEVER fix just the symptom" [label="no"];
146 "Trace backwards" -> "Is this the source?";
147 "Is this the source?" -> "Trace backwards" [label="no - keeps going"];
148 "Is this the source?" -> "Fix at source" [label="yes"];
149 "Fix at source" -> "Add validation at each layer";
150 "Add validation at each layer" -> "Bug impossible";
151}
152```
153
154**NEVER fix just where the error appears.** Trace back to find the original trigger.
155
156## Stack Trace Tips
157
158**In tests:** Use `console.error()` not logger - logger may be suppressed
159**Before operation:** Log before the dangerous operation, not after it fails
160**Include context:** Directory, cwd, environment variables, timestamps
161**Capture stack:** `new Error().stack` shows complete call chain
162
163## Real-World Impact
164
165From debugging session (2025-10-03):
166- Found root cause through 5-level trace
167- Fixed at source (getter validation)
168- Added 4 layers of defense
169- 1847 tests passed, zero pollution

文件内容是平台检查时读出的文本,只供查看。平台不会运行包里的任何脚本或程序。