Root cause analysis (RCA)
You are an engineer leading a root cause investigation. Your task is not to describe the
symptom and not to propose the first fix that comes to hand, but to demonstrably establish
WHY the defect became possible, and to propose both a specific patch and a systemic
prevention of the entire class of problem. A blameless tone: we examine the system and the
process, not who is to blame — people act rationally within the tools and information
given to them.
The discipline is adversarial (as in bug-report-verify): do not accept the first
plausible hypothesis as the cause. Prove each link of the "why" chain with
code (file:line), a log, git history or reproduction. A hypothesis without
evidence is a guess, not RCA; mark it as a hypothesis until you have confirmed it.
INPUT / SCOPE (how to determine the perimeter)
$ARGUMENTS (or the conversation context) comes in one of the forms — determine which,
and gather the facts:
- A. BUG / INCIDENT IN THE TRACKER (issue ID or link): get the text,
comments, status history via the available integration mechanism —
an MCP tool, if connected (for example YouTrack MCP —
youtrack_get_issue; Jira/GitHub/Linear similarly), otherwise ask the
user to paste the description and related links. Find related commits by the
ticket ID: git log --all --grep=<ISSUE-ID> --oneline, then
git show --stat <hash>.
- B. LOG / STACK TRACE / ARTIFACT (path to a log file, dump, trace, or
pasted text): extract the point of failure (exception, file:line, timestamp,
correlation-id), and from it unwind the chain in the code.
- C. A FAILED TEST (test name/path, or CI run output): read the test
itself and the code under it; separate "the test catches a real bug" from "the test is
flaky/stale". Run it locally if possible.
- D. A SYMPTOM DESCRIPTION in words (without artifacts): first reproduce or
gather the missing facts (log, steps), do not build an RCA on a retelling.
Record the SCOPE at the start of the report: what we are examining (the symptom in one phrase),
which artifacts are on hand (log/trace/ticket/test/commits), which environment, the time
window of the incident. If the facts are insufficient to reach the cause
demonstrably (no logs, no access to the environment, the symptom is unclear) — stop
and list what needs to be gathered, instead of guessing.
The perimeter is wider than the literal symptom: include the calling
code, data consumers, neighboring modules of the same class, the configuration and the
environment where it manifested.
KEY PRINCIPLE: SYMPTOM ≠ CAUSE
The analysis fails when you stop at the first layer ("it crashed due to a
NullPointerException" — that is a symptom, not a cause). Keep three levels separate:
- Proximate cause — what technically broke at the
moment of failure (which line threw the exception, which query returned the wrong thing).
- Root cause — why this became POSSIBLE (why null arrived at the
input; why there was no validation; why the contract diverged). Usually
3–5 "whys" deeper than the symptom.
- Contributing factors — what aggravated it or helped it pass
unnoticed (absence of a test, weak monitoring, release rush,
an ambiguous requirement).
The "why" stopping rule: dig while the next "why" is still within
your control and suggests an action. Stop when you hit an
external fact or a meaningful systemic decision. Do not turn 5 Whys into
finger-in-the-air guessing: every link is proven, not assumed.
METHODOLOGY
Work in order; delegate heavy steps (unwinding the code, bisect) at large
volume to subagents via the Agent tool, passing them concrete paths and
sections of this skill.
- Assemble the incident timeline. Reconstruct the chronology from facts: when
what was deployed / when the first errors appeared (logs, metrics, alerts) /
when it was noticed / what was done during the investigation / when it was stabilized. The timeline
often itself points to the introducing change (errors started 10 minutes
after deploy X).
- Pin down the symptom precisely. What exactly is observed, is it reproducible,
under which input data/environment. If you can — reproduce it minimally
(test/script/query) and record the actual result. Non-reproducibility
is also a fact (a race, specific data, prod only).
- 5 Whys — demonstrably. Build a chain from the symptom inward. To EACH
"why" attach evidence (file:line, log, git). Example skeleton:
- Why did the query crash? → The DB returned 0 rows, the code did not handle the empty list
(
service/x.py:42).
- Why did an empty list arrive? → The filter by company_id got None.
- Why None? → The request context did not propagate the tenant to the background task.
- Why did it not propagate? → The background worker was added after the main context and
did not pick up the middleware (
worker/y.py:88, commit abc123).
- Why was this not noticed? → There is no test for the background path with tenant isolation.
The first and last lines are the input for different conclusions (the fix and the prevention).
- Ishikawa / fishbone — check all categories of causes, so as not to
fixate on "it is the code's fault". Go through the categories and note the contribution of
each (or explicitly "not involved"):
- Code — logic, error handling, contract/types, concurrency.
- Data — incorrect/unexpected/"dirty" data, migration,
boundary values, volume.
- Configuration — flags, env, limits, timeouts, differences between environments.
- Environment / infra — runtime version, network, resources (OOM/CPU), an external
service, deploy.
- Process — review, testing, the release process, rollback.
- Requirements — ambiguous/incomplete/contradictory spec, the wrong case
implemented.
- Human factor — but blameless: not "Vasya made a mistake", but "the system
allowed this mistake to be made and not caught".
- Localize the introducing change in the code. If the bug is a regression: find the
commit that introduced it.
git log -p -- <file>, git blame <file> -L <lines>, and where reproducible — git bisect start / bad / good <ref>,
to reach the commit via binary search. Record the hash, the author-context (without
blame), what exactly changed and why it looked safe at the time.
- Separate the three levels of cause (proximate / root /
contributing) explicitly — this is the core of the conclusion.
- "Why was it not caught?" — a separate mandatory examination. What was missing
for the defect not to reach prod:
- which test (unit/integration/E2E/regression) was absent or did not
cover this case/branch/boundary;
- which check at review/lint/types/contract was missing;
- which gate in CI/monitoring/alert was missing to catch it earlier.
This block is a direct input for the test-design and coverage-analysis skills
(
test-case-design, coverage gap analysis): formulate specifically
which test/check to add.
- Recommendations at two levels:
- Local fix — what specifically to correct to eliminate this
defect (file:line, the gist of the change). Do NOT make the edit yourself — this is a
read-only analysis.
- Systemic prevention of the class — what will keep the entire class of such
problems from recurring: the missing test, a lint rule, a standard contract,
a CI gate, a change to the process/template, a config default. It is this
level that distinguishes an RCA from "just fixed it".
DISTINCTION: A REAL BUG vs FLAKY / TEST ARTIFACT
If the input is a failed test, before building a product RCA, prove that the bug is in the
product, not in the test:
- does the failure reproduce stably or flicker (flaky: timeouts, races,
dependence on order/timing/an external service, unmocked random/date);
- is the test itself stale (an assert for old behavior that was deliberately
changed) — then the cause is in the test/the test-update process, not in the product;
- is it a shared resource between tests (shared state, a non-isolated DB).
Qualify it explicitly: "bug in the product" / "bug in the test" / "flaky infrastructure".
EDGE CASES OFTEN MISSED IN RCA
- Stopped at the proximate cause and called it the root cause (fixed the
symptom, the class of problem remained).
- A single "cause" when there are several contributing factors — incidents
rarely have one cause; fixing one does not close the window if the rest are in place.
- Confirmation bias: found a plausible hypothesis and stopped digging, without
disproving the alternatives. Actively look for a refutation of your version.
- "Cause" = the last commit by time, without bisect evidence that
it exactly introduces the defect (two changes may have coincided).
- A latent bug: the defective code lived for a long time, and it was "broken" by a change in DATA/
load/configuration, not a commit in this file — do not blame an innocent
commit.
- The cause is in the environment/config (a difference between prod and staging), while the analysis is
conducted from the code only.
- A race/concurrency: reproduces only under load; "cannot repeat
locally" ≠ "there is no bug".
- An external dependency (a third-party API/service changed behavior) — the root
cause is outside your code, but the prevention (timeout/retry/degradation) is
inside.
- A cascade: the primary failure triggered secondary ones; do not take a secondary symptom for
the root. Identify the first link by the timeline.
- "Why was it not caught" is replaced with "let us add more tests in general" — a
SPECIFIC missing case/boundary/branch is needed, not a slogan.
- Blame instead of blameless: the conclusion "a person was careless" does not suggest a
systemic action and harms the culture — reformulate it in terms of the system.
- There was already a fix, but it did not help/was rolled back — work out why the previous
cause hypothesis was wrong (this is itself a finding).
RCA QUALITY CRITERIA (DoD)
An RCA is considered complete only if:
- the symptom is reproduced OR it is explicitly explained why reproduction is impossible;
- the "why" chain is taken to the level where the next step is already an external fact
or a systemic decision, and EVERY link is proven;
- the proximate / root / contributing causes are separated;
- there is a "why was it not caught" section with a specific control gap;
- recommendations are given at TWO levels (patch + prevention of the class);
- action items have an owner (or a note "owner not determined —
needs assigning") and a priority.
REPORT FORMAT (postmortem)
Save the report to docs/qa/rca/<incident-slug>.md (slug — by the incident/
ticket ID or a short name). Before creating it, check the repository structure and
follow it; docs/qa/rca/ is the default. If an analysis of this incident already exists —
extend it, do not recreate it.
Postmortem structure:
- Brief summary — what happened, who/what was affected, what the scale and
duration were, what the root cause is in one phrase. No jargon, readable
for management.
- SCOPE / input — what was examined, which artifacts are on hand,
the environment, the time window.
- Timeline — the chronology from facts with timestamps (deploy → first errors →
detection → stabilization).
- Symptom — what was observed, reproducibility, input data.
- Cause analysis — 5 Whys (with evidence for each link) +
a fishbone breakdown by category; explicitly: proximate / root /
contributing. The introducing commit (hash) if a regression.
- Why it was not caught — the specific gap in tests/review/CI/monitoring.
- Recommendations — a table: patch (local fix) and systemic
prevention of the class; for each — the level, the gist, a reference to file:line
if applicable.
- Action items — a list of actions with an owner and a priority (P0..P3);
the tests/gates that need adding, break out separately as an input for
test-case-design/coverage analysis.
- What was not checked / limitations — no access to prod logs, did not
reproduce live, hypotheses that remain unproven (mark them as
hypotheses, not facts).
FORMATTING RULES
- Every link of the causal chain — with a reference to evidence (file:line,
a log with timestamp/line, a git hash, a reproduction result). The unproven,
mark with the word "hypothesis".
- Blameless: formulate in terms of the system/process, not individuals.
- Separate fact and conclusion: "in the log X" (fact) vs "probably, due to Y" (conclusion).
- Action items without an owner are useless — if the owner is unknown, write it out
as "assign an owner", do not leave it empty.
This is an analysis, not implementation: the fix and the preventive changes are made by the
team following the RCA — do not edit the code within this skill (delete temporary
scripts/tests for reproduction after checking).
1---2name: en-213description: Root cause analysis (RCA) of a defect, incident or failed test based on facts — proving the cause with code/logs/reproduction rather than guessing, separating the proximate and the root cause, using 5 Whys and Ishikawa/fishbone techniques, localizing the introducing commit via git bisect and a separate examination of "why the tests did not catch it". Use when asked to find the root cause of a bug/incident, do an RCA, work out "why this actually broke", run a 5 Whys, write an incident postmortem, understand how a defect slipped past tests and review, or why a fix did not help. Works with any tracker (Jira/YouTrack/GitHub Issues/Linear) via an available MCP tool or pasted data. This is NOT `bug-report-verify` (which proves that a bug is real) and not `bugfix-audit` (which checks an already-made fix) — here the goal is to establish and prove the CAUSE, and to systematically prevent the class of problem. Trigger even without the word "RCA", for example "why could this even happen", "dig down to the root", "how di4---5# Root cause analysis (RCA)67You are an engineer leading a root cause investigation. Your task is not to describe the8symptom and not to propose the first fix that comes to hand, but to demonstrably establish9WHY the defect became possible, and to propose both a specific patch and a systemic10prevention of the entire class of problem. A blameless tone: we examine the system and the11process, not who is to blame — people act rationally within the tools and information12given to them.1314The discipline is adversarial (as in `bug-report-verify`): do not accept the first15plausible hypothesis as the cause. Prove each link of the "why" chain with16code (file:line), a log, git history or reproduction. A hypothesis without17evidence is a guess, not RCA; mark it as a hypothesis until you have confirmed it.1819## INPUT / SCOPE (how to determine the perimeter)2021`$ARGUMENTS` (or the conversation context) comes in one of the forms — determine which,22and gather the facts:2324- **A. BUG / INCIDENT IN THE TRACKER** (issue ID or link): get the text,25 comments, status history via the available integration mechanism —26 an MCP tool, if connected (for example YouTrack MCP —27 `youtrack_get_issue`; Jira/GitHub/Linear similarly), otherwise ask the28 user to paste the description and related links. Find related commits by the29 ticket ID: `git log --all --grep=<ISSUE-ID> --oneline`, then30 `git show --stat <hash>`.31- **B. LOG / STACK TRACE / ARTIFACT** (path to a log file, dump, trace, or32 pasted text): extract the point of failure (exception, file:line, timestamp,33 correlation-id), and from it unwind the chain in the code.34- **C. A FAILED TEST** (test name/path, or CI run output): read the test35 itself and the code under it; separate "the test catches a real bug" from "the test is36 flaky/stale". Run it locally if possible.37- **D. A SYMPTOM DESCRIPTION in words** (without artifacts): first reproduce or38 gather the missing facts (log, steps), do not build an RCA on a retelling.3940Record the SCOPE at the start of the report: what we are examining (the symptom in one phrase),41which artifacts are on hand (log/trace/ticket/test/commits), which environment, the time42window of the incident. If the facts are insufficient to reach the cause43demonstrably (no logs, no access to the environment, the symptom is unclear) — stop44and list what needs to be gathered, instead of guessing.4546The perimeter is wider than the literal symptom: include the calling47code, data consumers, neighboring modules of the same class, the configuration and the48environment where it manifested.4950## KEY PRINCIPLE: SYMPTOM ≠ CAUSE5152The analysis fails when you stop at the first layer ("it crashed due to a53NullPointerException" — that is a symptom, not a cause). Keep three levels separate:54551. **Proximate cause** — what technically broke at the56 moment of failure (which line threw the exception, which query returned the wrong thing).572. **Root cause** — why this became POSSIBLE (why null arrived at the58 input; why there was no validation; why the contract diverged). Usually59 3–5 "whys" deeper than the symptom.603. **Contributing factors** — what aggravated it or helped it pass61 unnoticed (absence of a test, weak monitoring, release rush,62 an ambiguous requirement).6364The "why" stopping rule: dig while the next "why" is still within65your control and suggests an action. Stop when you hit an66external fact or a meaningful systemic decision. Do not turn 5 Whys into67finger-in-the-air guessing: every link is proven, not assumed.6869## METHODOLOGY7071Work in order; delegate heavy steps (unwinding the code, bisect) at large72volume to subagents via the Agent tool, passing them concrete paths and73sections of this skill.74751. **Assemble the incident timeline.** Reconstruct the chronology from facts: when76 what was deployed / when the first errors appeared (logs, metrics, alerts) /77 when it was noticed / what was done during the investigation / when it was stabilized. The timeline78 often itself points to the introducing change (errors started 10 minutes79 after deploy X).802. **Pin down the symptom precisely.** What exactly is observed, is it reproducible,81 under which input data/environment. If you can — reproduce it minimally82 (test/script/query) and record the actual result. Non-reproducibility83 is also a fact (a race, specific data, prod only).843. **5 Whys — demonstrably.** Build a chain from the symptom inward. To EACH85 "why" attach evidence (file:line, log, git). Example skeleton:86 - Why did the query crash? → The DB returned 0 rows, the code did not handle the empty list87 (`service/x.py:42`).88 - Why did an empty list arrive? → The filter by company_id got None.89 - Why None? → The request context did not propagate the tenant to the background task.90 - Why did it not propagate? → The background worker was added after the main context and91 did not pick up the middleware (`worker/y.py:88`, commit abc123).92 - Why was this not noticed? → There is no test for the background path with tenant isolation.93 The first and last lines are the input for different conclusions (the fix and the prevention).944. **Ishikawa / fishbone — check all categories of causes**, so as not to95 fixate on "it is the code's fault". Go through the categories and note the contribution of96 each (or explicitly "not involved"):97 - **Code** — logic, error handling, contract/types, concurrency.98 - **Data** — incorrect/unexpected/"dirty" data, migration,99 boundary values, volume.100 - **Configuration** — flags, env, limits, timeouts, differences between environments.101 - **Environment / infra** — runtime version, network, resources (OOM/CPU), an external102 service, deploy.103 - **Process** — review, testing, the release process, rollback.104 - **Requirements** — ambiguous/incomplete/contradictory spec, the wrong case105 implemented.106 - **Human factor** — but blameless: not "Vasya made a mistake", but "the system107 allowed this mistake to be made and not caught".1085. **Localize the introducing change in the code.** If the bug is a regression: find the109 commit that introduced it. `git log -p -- <file>`, `git blame <file> -L110 <lines>`, and where reproducible — `git bisect start / bad / good <ref>`,111 to reach the commit via binary search. Record the hash, the author-context (without112 blame), what exactly changed and why it looked safe at the time.1136. **Separate the three levels of cause** (proximate / root /114 contributing) explicitly — this is the core of the conclusion.1157. **"Why was it not caught?"** — a separate mandatory examination. What was missing116 for the defect not to reach prod:117 - which test (unit/integration/E2E/regression) was absent or did not118 cover this case/branch/boundary;119 - which check at review/lint/types/contract was missing;120 - which gate in CI/monitoring/alert was missing to catch it earlier.121 This block is a direct input for the test-design and coverage-analysis skills122 (`test-case-design`, coverage gap analysis): formulate specifically123 which test/check to add.1248. **Recommendations at two levels:**125 - **Local fix** — what specifically to correct to eliminate this126 defect (file:line, the gist of the change). Do NOT make the edit yourself — this is a127 read-only analysis.128 - **Systemic prevention of the class** — what will keep the entire class of such129 problems from recurring: the missing test, a lint rule, a standard contract,130 a CI gate, a change to the process/template, a config default. It is this131 level that distinguishes an RCA from "just fixed it".132133## DISTINCTION: A REAL BUG vs FLAKY / TEST ARTIFACT134135If the input is a failed test, before building a product RCA, prove that the bug is in the136product, not in the test:137- does the failure reproduce stably or flicker (flaky: timeouts, races,138 dependence on order/timing/an external service, unmocked random/date);139- is the test itself stale (an assert for old behavior that was deliberately140 changed) — then the cause is in the test/the test-update process, not in the product;141- is it a shared resource between tests (shared state, a non-isolated DB).142Qualify it explicitly: "bug in the product" / "bug in the test" / "flaky infrastructure".143144## EDGE CASES OFTEN MISSED IN RCA145146- Stopped at the proximate cause and called it the root cause (fixed the147 symptom, the class of problem remained).148- A single "cause" when there are several contributing factors — incidents149 rarely have one cause; fixing one does not close the window if the rest are in place.150- Confirmation bias: found a plausible hypothesis and stopped digging, without151 disproving the alternatives. Actively look for a refutation of your version.152- "Cause" = the last commit by time, without bisect evidence that153 it exactly introduces the defect (two changes may have coincided).154- A latent bug: the defective code lived for a long time, and it was "broken" by a change in DATA/155 load/configuration, not a commit in this file — do not blame an innocent156 commit.157- The cause is in the environment/config (a difference between prod and staging), while the analysis is158 conducted from the code only.159- A race/concurrency: reproduces only under load; "cannot repeat160 locally" ≠ "there is no bug".161- An external dependency (a third-party API/service changed behavior) — the root162 cause is outside your code, but the prevention (timeout/retry/degradation) is163 inside.164- A cascade: the primary failure triggered secondary ones; do not take a secondary symptom for165 the root. Identify the first link by the timeline.166- "Why was it not caught" is replaced with "let us add more tests in general" — a167 SPECIFIC missing case/boundary/branch is needed, not a slogan.168- Blame instead of blameless: the conclusion "a person was careless" does not suggest a169 systemic action and harms the culture — reformulate it in terms of the system.170- There was already a fix, but it did not help/was rolled back — work out why the previous171 cause hypothesis was wrong (this is itself a finding).172173## RCA QUALITY CRITERIA (DoD)174175An RCA is considered complete only if:176- the symptom is reproduced OR it is explicitly explained why reproduction is impossible;177- the "why" chain is taken to the level where the next step is already an external fact178 or a systemic decision, and EVERY link is proven;179- the proximate / root / contributing causes are separated;180- there is a "why was it not caught" section with a specific control gap;181- recommendations are given at TWO levels (patch + prevention of the class);182- action items have an owner (or a note "owner not determined —183 needs assigning") and a priority.184185## REPORT FORMAT (postmortem)186187Save the report to `docs/qa/rca/<incident-slug>.md` (slug — by the incident/188ticket ID or a short name). Before creating it, check the repository structure and189follow it; `docs/qa/rca/` is the default. If an analysis of this incident already exists —190extend it, do not recreate it.191192Postmortem structure:1931941. **Brief summary** — what happened, who/what was affected, what the scale and195 duration were, what the root cause is in one phrase. No jargon, readable196 for management.1972. **SCOPE / input** — what was examined, which artifacts are on hand,198 the environment, the time window.1993. **Timeline** — the chronology from facts with timestamps (deploy → first errors →200 detection → stabilization).2014. **Symptom** — what was observed, reproducibility, input data.2025. **Cause analysis** — 5 Whys (with evidence for each link) +203 a fishbone breakdown by category; explicitly: proximate / root /204 contributing. The introducing commit (hash) if a regression.2056. **Why it was not caught** — the specific gap in tests/review/CI/monitoring.2067. **Recommendations** — a table: patch (local fix) and systemic207 prevention of the class; for each — the level, the gist, a reference to file:line208 if applicable.2098. **Action items** — a list of actions with an owner and a priority (P0..P3);210 the tests/gates that need adding, break out separately as an input for211 `test-case-design`/coverage analysis.2129. **What was not checked / limitations** — no access to prod logs, did not213 reproduce live, hypotheses that remain unproven (mark them as214 hypotheses, not facts).215216## FORMATTING RULES217218- Every link of the causal chain — with a reference to evidence (file:line,219 a log with timestamp/line, a git hash, a reproduction result). The unproven,220 mark with the word "hypothesis".221- Blameless: formulate in terms of the system/process, not individuals.222- Separate fact and conclusion: "in the log X" (fact) vs "probably, due to Y" (conclusion).223- Action items without an owner are useless — if the owner is unknown, write it out224 as "assign an owner", do not leave it empty.225226This is an analysis, not implementation: the fix and the preventive changes are made by the227team following the RCA — do not edit the code within this skill (delete temporary228scripts/tests for reproduction after checking).