Debugging & Incidents

Root Cause Analyzer

Trace a reproduced bug back to its actual root cause using the code path, not assumption -- and check whether the obvious fix addresses the cause or just the symptom.

When to use this

  • A bug has a reliable reproduction and needs its actual cause identified before fixing.
  • A quick patch fixed the symptom but the same class of bug keeps recurring elsewhere.
  • Not for the initial "is this even reproducible" step -- use bug-reproducer first.

The skill file

Copy this verbatim. It's written in the SKILL.md format (YAML frontmatter + markdown instructions) that Claude Code, and increasingly other agent tools, read directly.

SKILL.md
---
name: root-cause-analyzer
description: Trace a reproduced bug to its actual root cause by following the real code path and data flow, distinguishing the root cause from downstream symptoms. Use after a bug is reliably reproduced, before writing a fix.
---

# Root Cause Analyzer

## Process

1. Start at the point where wrong behavior is observed, and walk
   backward through the actual call stack / data flow -- not the code you
   assume is involved, the code that's actually on the path, confirmed by
   reading it or adding targeted logging/breakpoints.
2. At each step, ask: is this component behaving correctly given its
   input? If yes, the bug is further upstream; if no, you may have found
   it, or another upstream cause of this component's bad input.
3. Keep going until you reach the actual point where something first goes
   wrong -- a wrong value, a missed check, an incorrect assumption in the
   code -- not just the first point where the *effect* becomes visible.
4. Distinguish root cause from contributing factors: a null-pointer crash
   might be "caused" by a missing null check, but the actual root cause
   could be further upstream (why was null even possible there).

## Common failure mode: fixing the symptom

Before proposing a fix, check: does this fix address the root cause, or
does it just stop the specific symptom from being visible while the
underlying wrong state/logic remains? A fix that suppresses an error
without addressing why the error condition occurs is a red flag.

## Output

The causal chain from root cause to observed symptom, each link
justified by code you actually read (not assumed), and a fix
recommendation that addresses the root, with a note on any contributing
factors worth fixing separately.

Installing it elsewhere

The frontmatter/body split above is Claude Code's convention. Here's how to carry the same instructions into other tools:

Claude Code
.claude/skills/root-cause-analyzer/SKILL.md

Save the file below verbatim (frontmatter included) at that path, project-local or in ~/.claude/skills/ for a user-level skill. Claude Code loads the name/description pair to decide when to pull it in, or you invoke it directly as /root-cause-analyzer.

Cursor
.cursor/rules/root-cause-analyzer.mdc

Convert the YAML frontmatter to Cursor's rule format (description, globs, alwaysApply: false) and keep the markdown body as the rule content. Cursor surfaces it by description match, same idea as Claude Code's auto-load.

Codex CLI / Copilot
AGENTS.md

Codex CLI (and increasingly other agentic CLIs) read AGENTS.md at the repo root as always-on instructions. Paste the markdown body under a heading like ## {title}; for GitHub Copilot's coding agent, the equivalent file is .github/copilot-instructions.md.

Windsurf
.windsurfrules

Append the markdown body to .windsurfrules at the repo root. Windsurf treats the whole file as always-on context, so keep only the instructions you want applied on every request.

Where this goes wrong
  • Stopping at the first plausible-looking cause instead of confirming it against the actual code.
  • Proposing a fix that suppresses the symptom (e.g. adding a null check) without asking why the invalid state was reachable in the first place.
  • Assuming the code behaves as documented/named without reading it, especially in unfamiliar or legacy modules.