Imported from envoydev/claude-stack (
stack/skills/project-diagnose-failure/SKILL.md). Install upstream withnpx skills add envoydev/claude-stack --skill project-diagnose-failure. Copyright stays with the author.
Diagnose Failure - one entry for any failure evidence
One reported failure, four steps, the user holds the gate between them. This skill owns the chain, the stops, the evidence accounting, and the fork at the end; the catalogues and the seats do the specialist work. It is READ-ONLY from start to finish - no step here writes code, so no approval stamp and no commit gate come into play.
Evidence tiers - name the tier, carry it as the confidence label
The tier is the first thing this skill establishes and the last thing its verdict is qualified by - tier 1 a stack trace or failing test, 2 a log window / red CI run / monitoring event, 3 written repro steps, 4 a screenshot or a prose report. No source is a lower tier, never a blocker. A tier-4 report is the DEFAULT case, not an edge case, and a conclusion drawn there is labelled as such, never presented with tier-1 confidence.
references/evidence-tiers.md carries the table (what each tier buys, and the code-first path a
tier-4 report takes) plus what this skill can and cannot reach - files, logs, the repo history,
the app itself, gh and an error-monitoring MCP where the project has one, and never a LINK.
Read it at step 1, before the tier is named.
State - two layers, split by durability
- The findings file (
<docs-path>/diagnoses/<slug>.md) is the durable truth: the failure as an observable, the evidence tier, every digest's key lines, the hypotheses with their verdicts, the proven cause, and the stamps this run adds (Tier,Gathered,Cause,Outcome). On any conflict with memory or the chat, the file wins. - The serena note (
write_memorynamed<slug>__diagnosis) is the working cursor: current step, chosen mode, resume pointer, the error signature and its proven fix once found - that last part is the reusable half, keyed to the signature, never a dump of the log.
On invocation, resume before starting: list_memories -> read_memory the slug's note (or
an equivalent direct read of .serena/memories/) and read the findings file's stamps. A run
mid-flight resumes at its cursor - never re-run a step already stamped. A run between steps 2
and 3 looks like:
findings <docs-path>/diagnoses/orders-sync-disposed.md:
Observable: nightly order sync stops after the first batch; expected all batches
Tier: 1 (ObjectDisposedException, frame OrderSyncJob.ExecuteAsync) | Gathered: 2 sources agree
Cause: <pending>
note 'orders-sync-disposed__diagnosis': step 3 ROOT CAUSE - mode inline, 1 hypothesis open
Mode - ask at start
When dispatch is available, ask ONE question before the evidence pass, via AskUserQuestion - gather through evidence-gatherer seats, or inline in this session? - unless a calling flow already picked the run's mode, which is inherited, never re-asked. Recommend the seats when the evidence is BIG or MANY (a multi-megabyte log, a CI dump, three sources to correlate): the whole point of that seat is that the raw volume never lands in this context. Recommend inline when a single command or a bounded grep settles it. An interrupted or declined ask is answered by RE-ASKING, never by inference.
The stop contract
A stop IS one AskUserQuestion call: report one line of result and the artifact path, then put the next move through the AskUserQuestion tool - EVERY stop, the plain step-done ones included. There is no non-decision stop: 'what happens next' is itself the decision. The options are concrete - the next step named, the route-back where a step surfaced gaps, the fresh-session resume on a long run - the recommendation marked per that stop's own rule, free text always available via the built-in Other. Where the harness has no such tool, list the same options in plain text and END THE TURN. The selected answer is the go; silence is not, and a stop that only narrates is not a stop. Once the run has crossed the install's fresh-session trigger for its context window (150,000 tokens on a 200k window, 400,000 on a 1M one, 180,000 on any other window) or spans hours, the fresh-session resume IS one of the next ask's options - a CONSTRUCTION check before emitting each stop, not a memory: resume needs only the findings file plus the note.
The steps
Each step that names a catalogue INVOKES it via the Skill tool - and re-invokes it in a new cycle in the same chat, even when an earlier cycle already loaded it.
- TRIAGE - read the evidence in whatever form it arrived (
Readopens a screenshot as readily as a log), restate the failure as an OBSERVABLE - what happened, where, what should have happened instead - and name the tier, perreferences/evidence-tiers.md. Then load the signature catalogue that matches the failure's origin, matched from YOUR skill list by what each skill says it covers, never by a remembered name: the one covering local-runtime crash signatures (null-reference, DI resolution, async deadlock, race, disposed lifecycle, config drift) for a failure on your own machine, the one covering red-pipeline signatures (compile/restore, green-locally-red-on-the-runner, quality gate, signing, workflow drift, infra flake) for a red CI run. Before writing 'none installed': re-read the skill list text once more end to end for a description match - a catalogue present in that list and skipped is the measured failure this line exists to catch (measured: a listing held the local-runtime catalogue and the findings file still said 'none installed'). Only when that recheck comes back empty does this project have no such catalogue - say so and proceed on the method alone. A red pipeline is the one route that leaves this skill: CI needs theghlog pull and the CI-versus-local environment delta, which is ci-failure-diagnoser's specialty - offer that dispatch as the recommended option at this stop rather than re-deriving it here. Write the findings file with the observable andTier: <n>. Stop. - GATHER - per the mode: dispatch one evidence-gatherer per source (reproduce this path,
pull and grep that log window, capture that screen), several in parallel when several
hypotheses need confirming, and reason over the compact digests they return; or run the
bounded commands inline. Correlate multiple sources on a shared key - a correlation/trace id,
a timestamp window, a release version - and say which sources agreed and which did not. At
tier 4, with no source to pull, this step is code-first instead: locate the named behaviour
with serena per
.claude/rules/baseline-navigation.md, read the paths that could produce the symptom, and attempt a repro. Never slurp a large log into this context - grep to the signal and quote a bounded window. If it cannot be reproduced, say so with what you tried, and work from the evidence and the code. Append the digests' key lines to the findings file, stampGathered:, then stop - put 'continue to root cause?' through the AskUserQuestion tool per 'The stop contract' above (the stop hook already enforces the call; measured: one run skipped this checkpoint and ran GATHER straight into ROOT CAUSE as a single 36-call stretch). - ROOT CAUSE - run the investigation through the hypothesis-and-test method
(
superpowers:systematic-debugging- hypotheses first, each one confirmed or killed against the code, where the install has it; the same loop as written here where it does not): form the fewest hypotheses the evidence supports, then confirm or kill each against the located code and the reproduction - root cause before symptom, never a plausible guess. Match the evidence to the catalogue's signature and isolate where the signature points, which is almost never the line that threw. Hard cap: 2 investigation passes. If the cause stays ambiguous after 2, stop guessing and record the surviving hypotheses RANKED with what would decide between each. StampCause:with the file + symbol and the evidence that proves it, orCause: unproven - <n> hypotheses ranked. Report a severity and an explicit P0-P3 priority when the ask was to level it rather than fix it. This step ends at the fork below - it never continues into planning on its own. - THE FORK - the step-3 stop asks ONE question whose options are the three real outcomes,
each named concretely. The recommendation is set by whether step 3 actually proved a cause -
proven recommends 4b, unproven recommends 4c - with the reason in the option's description:
- 4a. REPORT ('Write a report on the issue') - finish the findings file as a standalone document: the observable, the evidence tier, the proven cause with its located symbol, the blast radius and who it affects, severity + priority, and what a fix would have to change. No task cards. This is the outcome when the fix is someone else's, or not now.
- 4b. PLAN TASKS ('Plan the fix as tasks') - decompose the minimal change per cause into
independent tasks, each with a contract: the files it owns, what it must not touch, its
acceptance criterion, and the
log_pointsthe fix must leave behind at the seam that failed. Name the target stack per task. The cards go into the findings file under a## Tasksheading - the file is the handoff, not the chat. The build is the user's next step, never yours: the close-out names/project-solve-taskwith the findings path as its input (it is slash-only - a model Skill call is refused - and its DESIGN step turns these cards into the gated plan). If the real fix is a redesign rather than a targeted change, say so and make that stack's solution-designer the option's route instead of planning it here; if it would change a shared contract, mark it BLOCKED_CONTRACT_CHANGE rather than planning a silent edit. - 4c. INSTRUMENT ('Add log points and re-run') - the outcome that turns 'no evidence' into
work instead of a guess: ONE task card carrying only the log points to add at the suspect
seam - the exact symbols, the levels, and the identifiers each line must carry - so the next
occurrence arrives one tier higher. Name the hypothesis each line is there to decide.
Stamp
Outcome:with the branch taken. A proven cause is a CLOSING point, not a midpoint: the findings file is durable and self-contained by construction, so whatever the fork picked, the next phase - writing the report, building the tasks, reading the new logs - starts in a FRESH session resumed from that path, and this stop offers it as an option in its own words ('resume from<findings path>in a fresh session'). Recommend it once the chat has run for hours or past that same trigger (measured: one 17h15m diagnosis chat carried FOUR auto-compactions - ~1.46M tokens of context dropped at 360-371k each - with the findings file durable from early in the run; every message after the cause was proven re-sent a history the file already held, and noStopgate can catch it because that session never closed). Stop - and this close-out stop carries anything pending: an unwritten task card, a source the user still has to paste, a sibling repo that needs the same fix (that handoff is a FILE - a task card under<docs-path>/cross-project-tasks/ - never chat-only prose). Delete the serena cursor note; keep the signature-to-fix note if the cause was proven, that is the reusable half.
Do not
- Never pass a stop without the user's explicit word, and never take the fork yourself - step 4 runs the branch the USER picked, and 'obviously they want the fix' is not an answer.
- Never end a stop's turn without its AskUserQuestion (or the plain-text fallback's option list).
- Never write code, edit a file under test, or run a destructive repro - one that applies a migration against a real database, seeds or deletes tracked files, or commits. If the only repro is destructive, say so and stop.
- Never present a tier-3 or tier-4 conclusion with tier-1 confidence, and never report a cause you did not prove as the answer - an honest 'unproven, here is what would decide it' with the 4c instrumentation card beats a plausible guess that sends the fix at the wrong symbol.
- Never state a source was checked that you could not reach - an unfetchable link, an absent monitoring MCP, and a log the project does not retain are each named, not silently skipped.
- Never keep run state only in chat: a stamp that is not in the findings file does not exist.