# Root Cause

> Establish why something actually happened by iterating why until the answer stops changing, with each link citing an observation - and know the two ways the method lies. Use before filing a bead off a failure, before proposing a rule or a gate to prevent a recurrence, when a fix addresses a symptom, or when a retro asks for a cause.

- Skill: `niksavis/root-cause` (Agent Skill)
- Install (CLI): `npx skillmds@latest add niksavis/root-cause`
- Raw SKILL.md: https://api.skillmd.com/api/skills/niksavis/root-cause/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: niksavis (https://skillmd.com/u/niksavis)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/niksavis/root-cause

---

<!-- Generated by `basicly skills-build` from skill.yaml. Do not edit; edit the source. -->

# Root Cause

## First: is this one occurrence?

**A single failure inside the normal range is common cause, and acting on it makes
things worse.** Deming's funnel experiment: adjusting a stable process in response to
ordinary variation "invariably increases variation in the results". Run this analysis
on a *signal*, not on an incident.

A signal is a point beyond 3σ, or a non-random pattern — a run, a trend — inside the
limits (NIST/SEMATECH e-Handbook §pmc31). In this repo the practical test is cheaper:
**has this shape happened before?** Query the ledger before you analyse.

```sh
jq -r 'select(.kind=="created")|.payload.title // empty' .basicly/ledger/events-*.jsonl \
  | rg -i '<the shape>'
rg -c '<the marker>' .basicly/usage/run-records.json
```

A record's title is on its `created` event and nowhere else, so a query over every
event returns nothing — and nothing reads as "no prior", which is the answer that ends
the analysis. Confirm the pipeline's first stage prints titles before believing a zero.

One occurrence with no prior: record it and stop. Filing a bead off a single event is
how twelve records came to name one truncation while none asked why the trigger fired.

## The method

Iterate *why* until the answer stops changing. Two rules make it worth doing:

1. **Every link cites an observation, not an inference.** A `file:line`, a command and
   its output, a ledger row, a recorded verdict. A link you reasoned to is where the
   chain stops being evidence and starts being a story.
2. **Stop at a cause you control.** If the next *why* leaves this repository — a
   vendor's decision, a person's attention, "the model was wrong" — you have left the
   zone where a control can be built. Back up one link and build there.

## The two ways it lies

**It yields exactly one path, chosen by the asker.** Card, *BMJ Quality & Safety* 2017,
is the repo's recorded caution: iterated-why is not reproducible between analysts, and
two competent people reach different causes from the same incident. The remedy is not
to abandon it but to **record the branch you did not take**. At any link where a
second answer was available, name it and say why you went the other way.

**It terminates on a person.** "The agent guessed", "someone forgot", "human error" —
each of those is a place the chain stopped early, because none of them is a thing you
can build a control against. Ask what let the guess reach a commit.

## Output contract

Three things, and prose is not one of them:

- **A named control** that would have refused this defect. Not "be careful", not "add a
  note" — a check, a gate, a schema field, a refusal.
- **Its tier**: `control` (fails a gate), `warning` (reports and continues), or
  `documentation` (a human must read it). A documentation-tier outcome is recorded as a
  **downgrade, with the reason no stronger control was available**.
- **The class of defects it covers** — the whole set, not this one instance. A control
  that covers exactly one incident is a patch wearing a rule's clothes.

Then the branch not taken, in one line.

## The counterfactual, which is what makes a cause a cause

A chain that ends somewhere plausible is not the same as a chain that ends somewhere
load-bearing. Two questions, and they are cheap because they are answerable from the
same observations you already cited:

- **Per cause: would removing this one block this pathway?** If the failure still
  reaches the same end with the cause removed, it is a condition you noticed, not a
  cause you found — say so and keep going.
- **At set level: would removing the retained set block the observed failure?** A set
  that leaves a route open has a link missing, and the missing link is the one worth
  the control.

This is the check a second party can run against your chain without re-deriving it,
which is why it is here rather than in the prose above.

## Where the answer goes

Under D25 an agent never lands catalog guidance without a human. The output is a
**diff against catalog YAML** — a fragment, a skill, a hook, a schema field — never
advice, so `catalog lint` and the projection checks bound what the human approves.

## Worked shape

```text
Signal      3 of 5 lanes bounced on a shared landing anchor, twice in one pass
Why 1       the rebase conflicted           (merge.py:722, conflicts= on the result)
Why 2       two lanes appended at one anchor (both scope globs name basicly.toml)
Why 3       decompose grouped by scope-glob overlap, which is file adjacency
Why 4       scope globs are the only structure the decomposer can see
STOP        we control that

Branch not taken: "the anchors should be generated" — real, but it fixes three files
rather than the slicing rule that keeps producing collisions.

Control     a plan-gate refusal when two children declare an overlapping anchor
Tier        control
Covers      every decomposition that slices by file adjacency, not just this anchor
```

