# Root cause tracing

Bugs often surface deep in a call chain: a file written to the wrong directory, a database opened
with the wrong path, a query built from an empty string. The instinct is to fix where the error
appears, which treats the symptom.

**Trace backward through the call chain until you find the original trigger, then fix at the source.**

Reach for this when the error happens far from the entry point, the stack trace is long, or it is
unclear where an invalid value came from.

## The process

**1. Observe the symptom.**

```
Error: git init failed in ~/project/packages/core
```

**2. Find the immediate cause.** What code directly produces this?

```typescript
await execFileAsync("git", ["init"], { cwd: projectDir });
```

**3. Ask what called it**, and keep walking up:

```
WorktreeManager.createSessionWorktree(projectDir, sessionId)
  ← Session.initializeWorkspace()
  ← Session.create()
  ← the test at Project.create()
```

**4. Follow the value, not just the frames.** What was actually passed?

`projectDir` was `""`. An empty string as `cwd` resolves to `process.cwd()`, which was the source
directory. The `git init` was never the bug.

**5. Find where the bad value was born.** That is the fix site.

## Where to stop

Stop tracing at the first point where the value could have been validated but was not, and where
fixing it prevents every downstream symptom rather than one of them. That is the source.

Where the chain leaves code you control (a library, a framework callback), you have hit a dead end.
Fix at the closest boundary you own, and say explicitly that you stopped there and why.

## Then consider a guard at the boundary

Fixing the source removes this bug. A cheap validation where the value enters the system removes the
whole class of it, and turns a confusing deep failure into an obvious early one:

```typescript
if (!projectDir) {
  throw new Error("projectDir is required and was empty");
}
```

Add the guard where it makes the failure legible, not at every layer. Validation repeated at five
levels is its own maintenance problem, and the deletion test applies: if removing the check just
moves the complexity, it was not earning its place.
