root-cause-tracing

Trace root causes of errors through deep call stacks with instrumentation.

Updated Oct 25, 2025
One-click install
npx skills add https://github.com/WesleyMFrederick/cc-workflows --skill root-cause-tracing-wesleymfrederick
Or copy as Structured Prompt for Agent
Please help me install this Agent Skill.
Skill: root-cause-tracing
Source: https://github.com/WesleyMFrederick/cc-workflows/tree/main/.claude/skills/root-cause-tracing
Command: npx skills add https://github.com/WesleyMFrederick/cc-workflows --skill root-cause-tracing-wesleymfrederick

SYSTEM DOCUMENTATION & REQUIREMENTS

💡 This Skill includes scripts (resource) components.

What problem does it solve?

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.

The Tracing Process

1. Observe the Symptom

Error: git init failed in /Users/jesse/project/packages/core

2. Find Immediate Cause

What code directly causes this?

await execFileAsync('git', ['init'], { cwd: projectDir });

3. Ask: What Called This?

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?

const context = setupCoreTest(); // Returns { tempDir: '' }
Project.create('name', context.tempDir); // Accessed before beforeEach!

Adding Stack Traces

When you can't trace manually, add instrumentation:

// 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:

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?)

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

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";
}

File: .claude/skills/root-cause-tracing/find-polluter.sh

#!/bin/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'
...

Frequently Asked Questions about root-cause-tracing

High-intent search queries and answers about installing and using this skill.

FAQPage Schema
How do I trace a bug to its root cause in a deep call stack?

Root-cause tracing follows the call chain backward from where an error surfaces to find the original trigger. Start by identifying the immediate cause (the code directly producing the error), then trace upward through callers asking what value was passed, and continue until you find where invalid data or incorrect behavior originated. Fix at the source, not the symptom.

When should I add instrumentation to debug nested execution paths?

Add instrumentation when manual tracing through the call stack is unclear, especially for bugs in sessions, worktrees, or test pollution. Insert logging before problematic operations to capture the full stack trace, variable state, and execution context. Use console.error in tests and grep to extract debug output for pattern analysis.

How do I prevent bugs from manifesting deep in execution after finding the root cause?

Apply defense-in-depth validation: add checks at each layer of the call chain so invalid data cannot propagate downward. Validate inputs at function entry points, use guards like NODE_ENV checks, and make preconditions explicit through early throws. This makes the bug impossible to trigger, not just harder to encounter.

What's the difference between fixing where an error appears and fixing its root cause?

Fixing the symptom patches the immediate failure but leaves the underlying trigger in place, allowing the bug to resurface in different forms. Root-cause fixing identifies and eliminates the original trigger, making entire classes of related failures impossible. Backward traceability through the call chain ensures you fix at the source.

Can I use stack traces to identify which test is creating unwanted file state or pollution?

Yes. Capture stack traces in debug output and look for test file names and line numbers. The stack shows the exact execution path triggering the operation. Combine this with bisection techniques to isolate which specific test creates pollution, then fix its setup or teardown.

How do I know when I've traced far enough up the call chain?

You've reached the root cause when you find the original trigger—the point where invalid data entered the system, a wrong parameter was passed, or a precondition was violated. Ask: does this caller receive the bad value from above, or does this layer create it? Stop when the answer is this layer creates it or depends on external initialization.