25d895d 🐛 fix(gitea_workflow): clean up temp repos after job steps 2bc3b11 🐛 fix(gitea_workflow): clean up temporary repo dirs in workflows 98c3f30 📝 docs(agent_rules): allow plan execution on current branch 16c7230 📝 docs(prompts): define custom verify layering 8efc4dd 🐛 fix(skills): quote commit-message description bc8498f 🐛 fix(ci): install tomli for gitea tests 55cda3b 🐛 fix(tests): report missing toml parser clearly c0729c7 🐛 fix(playbook): import Optional for cli compatibility 63e24bf 📦 deps(skills): sync thirdparty skills d2f9356 🐛 fix(ci): isolate gitea workflow repos 588b81d 🐛 fix(ci): inline gitea workflow bootstrap e0b1c3a ♻️ refactor(skills): standardize first-party skill contracts 2c5050d ♻️ refactor(skills): rename repo skills source dir f049dfb 📦 deps(skills): drop duplicate first-party superpowers skills 234b335 ✨ feat(workflow): add superpowers planning and execution state tracking c1702a6 📝 docs(markdown): format tracked markdown and drop stale templates 2325409 📝 docs(markdown): clarify optional markdownlint usage 214c44e 🔧 chore(markdown): add markdownlint baseline and lint fixes a22b324 📝 docs(templates): add execution and memory-bank prompt templates 223a797 📝 docs(templates): update README for Claude Code and current features 4ac8672 📝 docs: simplify README + platform-agnostic tools + auto-create local rules 2431c9d 📝 docs: add claude_md config and use cross-platform paths d64b248 📝 docs: fix README.md inaccuracies and add Claude Code info c8d6bf2 🐛 fix(playbook): use relative paths in CLAUDE.md when not at project root 6518f0f ✨ feat(playbook): auto-create CLAUDE.md with path discovery 6ec9a45 ✨ feat(skills): add skill_link symlink support + platform-agnostic prompt 9f8b6b5 📝 docs: update README and config example for Claude Code support 79cff6c 📝 docs(skills): add Claude Code platform support 452c6f5 ✨ feat(playbook): auto-inject AGENTS.md into CLAUDE.md e1dbf3c 🐛 fix(skills): remove dual-path from commit-message skill f3a7259 🔧 chore(ci): use prepare_repo.sh in both workflows da08212 🔧 chore(ci): extract prepare_repo.sh and clean up workflows 7ade85e 🗑️ remove(tsl): drop syntax_book/, data/ source and build script f94dba0 ♻️ refactor(skills): update playbook.py and tests for thirdparty/ layout b3df412 ♻️ refactor(skills): separate thirdparty skills into thirdparty/ subdirectory 64950e7 📦 deps(skills): sync thirdparty skills a2e3cb0 ✨ feat(playbook): add no_backup deploy controls 8609d59 🐛 fix(docs): repair reference catalog source links 956da11 🐛 fix(playbook): publish hidden ci test fixes 3f67754 📦 deps(skills): sync thirdparty skills 08ca87b 📦 deps(skills): add karpathy thirdparty sync 96b705b 📝 docs(tsl): rebuild canonical syntax and routing manual 3ed5052 📦 deps(skills): sync thirdparty skills 60108dd 📦 deps(skills): sync thirdparty skills da85d4e 🐛 fix(thirdparty): prune nested project snapshots a2a697e 📦 deps(skills): sync thirdparty skills 9df610a 🐛 fix(thirdparty): exclude duplicated superpowers skills 33dd5bb 🐛 fix(thirdparty): preserve optional manifest fields 91b0ea7 🐛 fix(thirdparty): preserve manifest during snapshot update 2e26f98 🔧 chore(thirdparty): generalize skills sync pipeline 5b9c1e3 📦 deps(skills): sync superpowers 2f2d34a 📝 docs(readme): normalize subtree command spacing 62db7db 🐛 fix(ci): serialize superpowers update and sync 3463223 🐛 fix(ci): use literal superpowers sync paths 48f6de8 📦 deps(skills): sync superpowers 4b23529 🔧 chore(ci): merge superpowers update and sync workflow a56d75b 📦 deps(skills): sync superpowers 84bcefa 🔧 chore(ci): use ci[bot] commit author name 00a07e5 📦 deps(skills): sync superpowers 7b84daf 🐛 fix(templates): enforce main loop progress tracking 51373d7 🔧 chore(ci): automate superpowers sync workflow eaaa39c 🐛 fix(ci): prevent stale superpowers sync from restoring skills block 79755c6 📦 deps(skills): sync superpowers 836d878 📦 deps(skills): sync superpowers 8216c9f 📦 deps(skills): sync superpowers 9439505 🐛 fix(playbook): address reported repo issues git-subtree-dir: docs/standards/playbook git-subtree-split: 25d895d8b3f56624ccfe99ad7289e9eb49e0f316
170 lines
5.2 KiB
Markdown
170 lines
5.2 KiB
Markdown
# Root Cause Tracing
|
|
|
|
## Overview
|
|
|
|
Bugs 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.
|
|
|
|
**Core principle:** Trace backward through the call chain until you find the original trigger, then fix at the source.
|
|
|
|
## When to Use
|
|
|
|
```dot
|
|
digraph when_to_use {
|
|
"Bug appears deep in stack?" [shape=diamond];
|
|
"Can trace backwards?" [shape=diamond];
|
|
"Fix at symptom point" [shape=box];
|
|
"Trace to original trigger" [shape=box];
|
|
"BETTER: Also add defense-in-depth" [shape=box];
|
|
|
|
"Bug appears deep in stack?" -> "Can trace backwards?" [label="yes"];
|
|
"Can trace backwards?" -> "Trace to original trigger" [label="yes"];
|
|
"Can trace backwards?" -> "Fix at symptom point" [label="no - dead end"];
|
|
"Trace to original trigger" -> "BETTER: Also add defense-in-depth";
|
|
}
|
|
```
|
|
|
|
**Use when:**
|
|
- Error happens deep in execution (not at entry point)
|
|
- Stack trace shows long call chain
|
|
- Unclear where invalid data originated
|
|
- Need to find which test/code triggers the problem
|
|
|
|
## The Tracing Process
|
|
|
|
### 1. Observe the Symptom
|
|
```
|
|
Error: git init failed in ~/project/packages/core
|
|
```
|
|
|
|
### 2. Find Immediate Cause
|
|
**What code directly causes this?**
|
|
```typescript
|
|
await execFileAsync('git', ['init'], { cwd: projectDir });
|
|
```
|
|
|
|
### 3. Ask: What Called This?
|
|
```typescript
|
|
WorktreeManager.createSessionWorktree(projectDir, sessionId)
|
|
→ called by Session.initializeWorkspace()
|
|
→ called by Session.create()
|
|
→ called by test at Project.create()
|
|
```
|
|
|
|
### 4. Keep Tracing Up
|
|
**What value was passed?**
|
|
- `projectDir = ''` (empty string!)
|
|
- Empty string as `cwd` resolves to `process.cwd()`
|
|
- That's the source code directory!
|
|
|
|
### 5. Find Original Trigger
|
|
**Where did empty string come from?**
|
|
```typescript
|
|
const context = setupCoreTest(); // Returns { tempDir: '' }
|
|
Project.create('name', context.tempDir); // Accessed before beforeEach!
|
|
```
|
|
|
|
## Adding Stack Traces
|
|
|
|
When you can't trace manually, add instrumentation:
|
|
|
|
```typescript
|
|
// Before the problematic operation
|
|
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 });
|
|
}
|
|
```
|
|
|
|
**Critical:** Use `console.error()` in tests (not logger - may not show)
|
|
|
|
**Run and capture:**
|
|
```bash
|
|
npm test 2>&1 | grep 'DEBUG git init'
|
|
```
|
|
|
|
**Analyze stack traces:**
|
|
- Look for test file names
|
|
- Find the line number triggering the call
|
|
- Identify the pattern (same test? same parameter?)
|
|
|
|
## Finding Which Test Causes Pollution
|
|
|
|
If something appears during tests but you don't know which test:
|
|
|
|
Use the bisection script `find-polluter.sh` in this directory:
|
|
|
|
```bash
|
|
./find-polluter.sh '.git' 'src/**/*.test.ts'
|
|
```
|
|
|
|
Runs tests one-by-one, stops at first polluter. See script for usage.
|
|
|
|
## Real Example: Empty projectDir
|
|
|
|
**Symptom:** `.git` created in `packages/core/` (source code)
|
|
|
|
**Trace chain:**
|
|
1. `git init` runs in `process.cwd()` ← empty cwd parameter
|
|
2. WorktreeManager called with empty projectDir
|
|
3. Session.create() passed empty string
|
|
4. Test accessed `context.tempDir` before beforeEach
|
|
5. setupCoreTest() returns `{ tempDir: '' }` initially
|
|
|
|
**Root cause:** Top-level variable initialization accessing empty value
|
|
|
|
**Fix:** Made tempDir a getter that throws if accessed before beforeEach
|
|
|
|
**Also added defense-in-depth:**
|
|
- Layer 1: Project.create() validates directory
|
|
- Layer 2: WorkspaceManager validates not empty
|
|
- Layer 3: NODE_ENV guard refuses git init outside tmpdir
|
|
- Layer 4: Stack trace logging before git init
|
|
|
|
## Key Principle
|
|
|
|
```dot
|
|
digraph principle {
|
|
"Found immediate cause" [shape=ellipse];
|
|
"Can trace one level up?" [shape=diamond];
|
|
"Trace backwards" [shape=box];
|
|
"Is this the source?" [shape=diamond];
|
|
"Fix at source" [shape=box];
|
|
"Add validation at each layer" [shape=box];
|
|
"Bug impossible" [shape=doublecircle];
|
|
"NEVER fix just the symptom" [shape=octagon, style=filled, fillcolor=red, fontcolor=white];
|
|
|
|
"Found immediate cause" -> "Can trace one level up?";
|
|
"Can trace one level up?" -> "Trace backwards" [label="yes"];
|
|
"Can trace one level up?" -> "NEVER fix just the symptom" [label="no"];
|
|
"Trace backwards" -> "Is this the source?";
|
|
"Is this the source?" -> "Trace backwards" [label="no - keeps going"];
|
|
"Is this the source?" -> "Fix at source" [label="yes"];
|
|
"Fix at source" -> "Add validation at each layer";
|
|
"Add validation at each layer" -> "Bug impossible";
|
|
}
|
|
```
|
|
|
|
**NEVER fix just where the error appears.** Trace back to find the original trigger.
|
|
|
|
## Stack Trace Tips
|
|
|
|
**In tests:** Use `console.error()` not logger - logger may be suppressed
|
|
**Before operation:** Log before the dangerous operation, not after it fails
|
|
**Include context:** Directory, cwd, environment variables, timestamps
|
|
**Capture stack:** `new Error().stack` shows complete call chain
|
|
|
|
## Real-World Impact
|
|
|
|
From debugging session (2025-10-03):
|
|
- Found root cause through 5-level trace
|
|
- Fixed at source (getter validation)
|
|
- Added 4 layers of defense
|
|
- 1847 tests passed, zero pollution
|