← All packs

Root-cause tracing: why the first plausible cause is usually wrong

Tracing a bug backward almost always stops too early. You find a line that throws, a null that shouldn't be null, a query that returns the wrong row — and it looks enough like an explanation that the trail ends there. The fix goes in at that layer, ships, and a similar bug reappears two weeks later from a different entry point. The original trace found a real cause; it just wasn't the root one. Three checks catch this before the fix goes in.

1. Proximate cause vs. root cause

The proximate cause is where the failure became visible: an exception, a wrong value, a crash. The root cause is the earliest point in the causal chain where a different decision would have prevented the failure and every variant of it. Signature of stopping too early: the fix is a null check, a try/catch, or a special case added at the exact line that threw, with no explanation for how the null got there in the first place.

Check: for the value that was wrong, ask "where was this value last known to be correct?" and walk forward from there, not backward from the crash. If you can't answer that question, you don't have the root cause yet — you have the crash site.

2. Necessary vs. sufficient

A contributing condition is necessary for the bug to occur but not sufficient to explain it — it needed something else to combine with. Signature: the "fix" removes one input to the bug, the bug stops reproducing in your test, but the same underlying defect can still be reached through a different combination of inputs you didn't test.

Check: for the condition you think caused it, ask "does removing only this, with everything else unchanged, make the class of bug impossible, or just this one instance of it?" If another combination of the same ingredients could still trigger it, you found a contributing condition, not the root cause.

3. Reproducibility at the claimed layer

If the root cause is correctly identified, a minimal repro built at that exact layer — not the layer where the symptom appeared — should reproduce the bug every time, with everything above it stripped away. Signature of a wrong diagnosis: the "fix" is applied, the original symptom disappears, but no one can construct a standalone repro of the mechanism itself.

Check: before shipping the fix, try to reproduce the bug using only the component you've blamed, isolated from the rest of the system. If you can't, the diagnosis is still a hypothesis, not a confirmed root cause.

Why this order matters

Check proximate-vs-root first because it's the cheapest to catch — it just requires refusing to stop at the first thing that looks like an explanation. Check necessary-vs-sufficient next because it requires actually enumerating other input combinations, which takes longer. Check reproducibility last, right before the fix ships, because it's the final gate: a root cause that can't be reproduced in isolation is still a guess wearing the fix's confidence.

None of this requires an LLM — it's three questions and a minimal repro. Where a model does help is on a large, unfamiliar codebase: walking the actual call graph and data flow backward from a stack trace to find where a value was last correct, instead of guessing from variable names, which is what the root-cause-tracer skill in the Claude Code Debug Pack does. If the bug only shows up intermittently, see the companion guide on diagnosing flaky tests; if it already caused an incident, see the one on blameless postmortems; if the "bug" is actually a slowdown, see the one on perf regression hunting; if you're starting from a raw pile of error logs rather than one known bug, see the one on error log triage; if the symptom is memory climbing over time rather than a single crash, see the one on memory leak hunting; if the bug only shows up under load or in a specific thread/task ordering, see the one on concurrency bug finding; if you're debugging an agent's own run rather than its code — a slow step, a redundant call, a loop with no clear start — see the one on tracing an agent's decision path.