Documentation
README
root-cause β read-only bug analysis
You are performing read-only analysis: no file edits, no branches, no
commands that mutate state. Output is a brief that a human or /fix acts on.
The iron rule: no fix without a root cause. A fix proposed before the cause is understood is a guess, and guesses create new bugs. Symptom patches are failure even when they make the error disappear.
Announce at start: "Using the root-cause skill β read-only analysis of this bug."
Step 0: Load context
- The bug report β full description, repro steps, linked threads/issues.
.ai/lessons.mdβ known pitfalls in the affected area.- Root
AGENTS.mdTask Router β the matching.claude/rules/guides. - The affected module/package
AGENTS.md.
Step 1: Establish the facts
- Read the error completely β full stack trace, message, HTTP status, file and line numbers. The error text often contains the answer.
- Reproduce or trace. State the exact repro steps. If you cannot reproduce even mentally, gather more data β do not guess.
- Check recent changes:
git log --oneline -20 -- <affected paths>and the branch diff. Most bugs live in what changed last. - Trace the data flow from entry point to origin: route β loader/action β
service function β query β response. Note every transformation. Follow the
bad value backward to where it is first wrong β fix at the source, not
where the symptom surfaces (see
references/root-cause-tracing.md). - Check the schema: read the newest relevant migrations (order by timestamp) and generated types. Column names and constraints must match what the code assumes.
Step 2: Carbon-specific failure modes
Check each of these before inventing exotic theories:
| Check | What to look for |
|---|---|
| companyId scoping | A query missing companyId filtering β cross-tenant leak or empty results |
| Stale generated types | Code references columns from a new migration but pnpm run generate:types wasn't run β typecheck greens lie |
| RLS policy gaps | New table/column without policies; check the table's migrations |
| Permission strings | requirePermissions() / permissions.can() scopes are string literals β invisible to typecheck; grep the whole repo after any scope rename |
| Service signature drift | Caller args don't match the service function's current signature |
| Migration ordering | A migration backdated older than deployed ones applies out of order on remotes (see .ai/lessons.md) |
| Form submission | ValidatedForm only submits on a native submit with a submitter; react-aria number/date fields commit hidden inputs on blur (see /test for details) |
| Import staleness | Import from a moved path with no re-export bridge |
Step 3: One hypothesis at a time
- Form a single specific hypothesis: "X in file Y causes the bug because Z."
- Verify it against the code you can read. If the code contradicts it, discard it β don't force-fit.
- Distinguish cause from symptom: a UI
TypeErrormay originate three layers down. Keep tracing until you reach the origin. - Three-strikes rule: if 3 hypotheses have failed, the problem is likely architectural (shared state, coupling, a wrong pattern) β STOP, write up what you ruled out, and surface the architectural question to the human instead of producing hypothesis #4.
Step 4: Confidence
| Level | Meaning |
|---|---|
| HIGH | Cause identified with code evidence; fix path obvious |
| MEDIUM | Strong code-supported hypothesis; runtime confirmation would help |
| LOW | Multiple plausible causes or the bug appears runtime/environment-dependent |
If MEDIUM or LOW and the bug involves runtime state, ordering, caching,
concurrency, or manual reproduction β recommend /debugging-difficult-bugs
(temporary JSONL instrumentation) as the next step instead of guessing. Never
present a guess as a finding.
Step 5: Output the brief
Produce exactly this structure (~400 words max):
## Root-Cause Brief
**Bug:** <one line>
**Summary:** <2β3 sentences: symptom and observable impact>
**Root cause:** <why it happens, citing file:line>
**Confidence:** HIGH | MEDIUM | LOW
<if not HIGH: what is uncertain and what would resolve it β e.g. "instrument via /debugging-difficult-bugs">
**Files to change:**
- `path/to/file.ts` β <what and why>
**Approach:**
1. <step>
**Risks:**
- <e.g. "signature change affects 3 callers">
**BC impact:** <NONE | FROZEN/STABLE surfaces touched, per BACKWARD_COMPATIBILITY.md>
Guardrails
- Read-only. No edits, no
gitwrites, no migrations, no DB commands. - No speculative fixes. "Try this and see" is not a finding.
- Stay scoped. Analyze the reported bug only; note unrelated discoveries in one line at the end, don't chase them.
- Cite evidence. Every claim references a file, line, migration, or policy.
Red flags β thinking any of these means you're guessing, not analyzing; STOP:
- "it's probably X, let me suggest the fix" (no verified cause β no fix)
- "I'll propose two possible fixes and let them pick" (that's two guesses)
- "one more hypothesis" after three have failed (that's an architecture question now β surface it)
- "the error message is misleading, ignore it" (read it again; it usually isn't)
References (read when the situation matches)
references/root-cause-tracing.mdβ tracing a bad value backward through the call stack to its originreferences/defense-in-depth.mdβ layering validation after the cause is foundreferences/condition-based-waiting.md(+condition-based-waiting-example.ts) β replacing arbitrary timeouts with condition polling in flaky async testsreferences/find-polluter.shβ bisecting which earlier test pollutes a failing test