Root Cause First
Use this skill to prevent premature patches. The goal is to make the smallest correct change only after the failure mechanism is understood.
Red Flags — Stop If You Catch Yourself Thinking
These thoughts mean you are about to ship a band-aid. Stop and find the mechanism first.
| Thought |
Reality |
"I'll add a ?. or null-check so it stops crashing" |
You are hiding the signal. Ask why it is null and fix that. |
| "Wrapping it in try/catch makes the error go away" |
Catch-and-continue turns a loud bug into a silent one. |
| "The test is flaky, let me add a retry or a longer timeout" |
Flaky means a real race or order dependency. Retry hides it. |
| "I'll widen the parser to accept the weird input too" |
The weird input is the clue. Find who produced it. |
| "I'll return a default on error so the flow continues" |
Synthetic success masks the failure from everyone downstream. |
| "It works now, I am not sure why" |
Then it is not fixed. You moved the bug. |
Relationship To SSOT Enforcer
If the root cause is duplicated constants, scattered policy, parallel calculations, copied parsing, or multiple modules answering the same question, switch to ssot-enforcer for the consolidation step. After consolidation, return here to verify the fix against the original failure.
When Not To Use
Do not use this skill for:
- New features or design work where there is no failure to explain.
- Mechanical edits such as renames, formatting, dependency bumps, or documentation cleanup.
- Broad architecture cleanup unless it is tied to a concrete symptom, failing test, bad runtime behavior, or user-reported regression.
Required Workflow
State the observed failure in concrete terms.
- Quote the exact error, log line, failed assertion, bad behavior, or user-visible symptom.
- Identify where in the execution path the failure happens.
Reconstruct the intended contract.
- Determine what the component is supposed to guarantee.
- Identify upstream inputs, downstream consumers, and invariants that must hold.
Trace the real path before editing.
- Read the code that produces the failure and the code that consumes its output.
- Prefer logs, tests, traces, raw API responses, local reproduction, and call sites over assumptions.
- Distinguish provider/tool behavior from adapter/parser/orchestrator behavior.
Name the root cause.
- Explain the mechanism that makes the observed failure inevitable or likely.
- If there are multiple plausible causes, list what evidence confirms or eliminates each one.
- Do not implement until there is one primary cause or a clearly bounded uncertainty.
Reject band-aids explicitly.
- Do not add fallback, retry, catch-and-continue, synthetic success, timeout inflation, broad parsing, or error suppression unless the root cause proves that behavior is the correct product contract.
- If such handling is truly needed, state why it is not masking the failure and what invariant it preserves.
Implement the root fix.
- Change the source of the bad state or the incorrect contract boundary.
- Keep the patch scoped to the root cause.
- If the source of the bad state is duplicated or scattered truth, consolidate it through
ssot-enforcer instead of adding a local workaround.
- Add diagnostics when future failures would otherwise be ambiguous.
Verify against the original failure.
- Add or update a regression test that fails on the old behavior.
- Run the narrow test first, then broader typecheck/test coverage as risk requires.
- If the fix cannot be fully verified in-environment (visual/UI/device-only behavior, or unavailable services), say so explicitly and name exactly what a human must check. Do not claim success you could not observe.
- In the final answer, report root cause, fix, and verification.
Worked Example
Symptom. TypeError: cannot read 'email' of undefined thrown in renderProfile() for ~2% of users.
Band-aid (what to avoid).
// Crash disappears, but those users now silently render a blank email.
const email = user.profile?.email ?? "";
The optional chain suppresses the only signal that something upstream is wrong.
Root-cause trace. Follow where user is built. loadUser() returns the record before profile is hydrated for accounts created before the profile backfill. The invariant "every user has a profile" is violated at the data layer, not in the view.
Root fix. Enforce the invariant at the source so every consumer can trust it:
// loadUser() now owns the invariant: it always returns a non-null profile.
function loadUser(id) {
const row = db.fetchUser(id);
return { ...row, profile: row.profile ?? defaultProfile(row.id) };
}
Now renderProfile() can trust its input and the ?. is unnecessary. (The invariant has exactly one owner — see ssot-enforcer.)
Verification. Add a regression test: loadUser() for a pre-backfill account returns a non-null profile; renderProfile() renders without optional chaining and the old input reproduces the original crash on the unpatched code.
Stop Conditions
Stop and ask or report a blocker when:
- The failure cannot be reproduced or located after reading the relevant path.
- Required credentials, services, logs, or artifacts are unavailable.
- Fixing the real cause requires a product decision rather than an engineering correction.
Final Answer Shape
Keep the close-out short:
Root cause: one concrete mechanism.
Fix: what changed and where.
Verification: commands/tests run and any remaining risk.
1---2name: root-cause-first3description: Root-cause-first debugging and repair workflow. Use when asked to fix a bug, runtime error, failing test, broken tool/agent behavior, unexpected logs, regression, flaky behavior, architecture issue, or any request that says "fix", "debug", "solve from the root", "no band-aids", "no fallback", or "understand what is happening before changing code". Do not use for greenfield feature work, mechanical edits, or cleanup without an observed failure.4---56# Root Cause First78Use this skill to prevent premature patches. The goal is to make the smallest correct change only after the failure mechanism is understood.910## Red Flags — Stop If You Catch Yourself Thinking1112These thoughts mean you are about to ship a band-aid. Stop and find the mechanism first.1314| Thought | Reality |15|---|---|16| "I'll add a `?.` or null-check so it stops crashing" | You are hiding the signal. Ask *why* it is null and fix that. |17| "Wrapping it in try/catch makes the error go away" | Catch-and-continue turns a loud bug into a silent one. |18| "The test is flaky, let me add a retry or a longer timeout" | Flaky means a real race or order dependency. Retry hides it. |19| "I'll widen the parser to accept the weird input too" | The weird input is the clue. Find who produced it. |20| "I'll return a default on error so the flow continues" | Synthetic success masks the failure from everyone downstream. |21| "It works now, I am not sure why" | Then it is not fixed. You moved the bug. |2223## Relationship To SSOT Enforcer2425If the root cause is duplicated constants, scattered policy, parallel calculations, copied parsing, or multiple modules answering the same question, switch to `ssot-enforcer` for the consolidation step. After consolidation, return here to verify the fix against the original failure.2627## When Not To Use2829Do not use this skill for:30- New features or design work where there is no failure to explain.31- Mechanical edits such as renames, formatting, dependency bumps, or documentation cleanup.32- Broad architecture cleanup unless it is tied to a concrete symptom, failing test, bad runtime behavior, or user-reported regression.3334## Required Workflow35361. State the observed failure in concrete terms.37 - Quote the exact error, log line, failed assertion, bad behavior, or user-visible symptom.38 - Identify where in the execution path the failure happens.39402. Reconstruct the intended contract.41 - Determine what the component is supposed to guarantee.42 - Identify upstream inputs, downstream consumers, and invariants that must hold.43443. Trace the real path before editing.45 - Read the code that produces the failure and the code that consumes its output.46 - Prefer logs, tests, traces, raw API responses, local reproduction, and call sites over assumptions.47 - Distinguish provider/tool behavior from adapter/parser/orchestrator behavior.48494. Name the root cause.50 - Explain the mechanism that makes the observed failure inevitable or likely.51 - If there are multiple plausible causes, list what evidence confirms or eliminates each one.52 - Do not implement until there is one primary cause or a clearly bounded uncertainty.53545. Reject band-aids explicitly.55 - Do not add fallback, retry, catch-and-continue, synthetic success, timeout inflation, broad parsing, or error suppression unless the root cause proves that behavior is the correct product contract.56 - If such handling is truly needed, state why it is not masking the failure and what invariant it preserves.57586. Implement the root fix.59 - Change the source of the bad state or the incorrect contract boundary.60 - Keep the patch scoped to the root cause.61 - If the source of the bad state is duplicated or scattered truth, consolidate it through `ssot-enforcer` instead of adding a local workaround.62 - Add diagnostics when future failures would otherwise be ambiguous.63647. Verify against the original failure.65 - Add or update a regression test that fails on the old behavior.66 - Run the narrow test first, then broader typecheck/test coverage as risk requires.67 - If the fix cannot be fully verified in-environment (visual/UI/device-only behavior, or unavailable services), say so explicitly and name exactly what a human must check. Do not claim success you could not observe.68 - In the final answer, report root cause, fix, and verification.6970## Worked Example7172**Symptom.** `TypeError: cannot read 'email' of undefined` thrown in `renderProfile()` for ~2% of users.7374**Band-aid (what to avoid).**7576```js77// Crash disappears, but those users now silently render a blank email.78const email = user.profile?.email ?? "";79```8081The optional chain suppresses the only signal that something upstream is wrong.8283**Root-cause trace.** Follow where `user` is built. `loadUser()` returns the record before `profile` is hydrated for accounts created before the profile backfill. The invariant "every user has a profile" is violated at the data layer, not in the view.8485**Root fix.** Enforce the invariant at the source so every consumer can trust it:8687```js88// loadUser() now owns the invariant: it always returns a non-null profile.89function loadUser(id) {90 const row = db.fetchUser(id);91 return { ...row, profile: row.profile ?? defaultProfile(row.id) };92}93```9495Now `renderProfile()` can trust its input and the `?.` is unnecessary. (The invariant has exactly one owner — see `ssot-enforcer`.)9697**Verification.** Add a regression test: `loadUser()` for a pre-backfill account returns a non-null `profile`; `renderProfile()` renders without optional chaining and the old input reproduces the original crash on the unpatched code.9899## Stop Conditions100101Stop and ask or report a blocker when:102- The failure cannot be reproduced or located after reading the relevant path.103- Required credentials, services, logs, or artifacts are unavailable.104- Fixing the real cause requires a product decision rather than an engineering correction.105106## Final Answer Shape107108Keep the close-out short:109- `Root cause:` one concrete mechanism.110- `Fix:` what changed and where.111- `Verification:` commands/tests run and any remaining risk.