failure-mode-analysis
Skill1 starsChanged 3 months agoMITView on GitHub
Enumerates the ways an operation can partially fail at design time, before the happy path is written, and specifies deliberate behavior for each one. Use when designing a feature or checkout flow that calls a database, queue, or third-party API. Use when asking what should happen if a dependency times out or is slow. Use when writing a design document or ADR for something with external dependencies. Use when reviewing code that calls an external service and deciding whether it is safe to retry, or how the system should degrade. NOT for diagnosing something that is already broken.
In this repository14 other files it gives its agents.
- api-compatibility-and-versioningSkill
- caching-and-stalenessSkill
- clock-and-time-correctnessSkill
- data-migration-and-backfillSkill
- distributed-data-consistencySkill
- distributed-locking-and-leasesSkill
- feature-flags-and-rollout-safetySkill
- idempotency-and-exactly-onceSkill
- money-movement-correctnessSkill
- multi-region-and-replication-lagSkill
- observability-for-correctnessSkill
- queue-semantics-and-replaySkill
- rate-limiting-and-quota-correctnessSkill
- resilience-patternsSkill
The file
We split this skill into the rules it gives an agent
5 rules. About 0 are common, 5 are distinctive, and 0 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈5
Distinctive · estimate
This author's own choices. Worth a look.
0
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Distinctive: line 21
After an incident, to find the sibling failures that have not happened yet
- Distinctive: line 22
Before adding a retry — retries change which failures are possible
- Distinctive: line 58
A blank cell is the finding: Do not move on until every cell has a decision.
- Distinctive: line 114
Every specified behavior needs a test that proves it.
- Distinctive: line 124
If a failure mode cannot be injected, it cannot be tested, and the specified behavior is a guess.
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Failure Mode Analysis
Overview
Most outages are not caused by a failure nobody could have predicted. They are caused by a failure nobody specified a behavior for — the dependency timed out, and the code did whatever fell out of the control flow.
This skill is the discipline of enumerating the ways an operation can partially fail before writing the happy path, and deciding — deliberately, in writing — what happens in each case. An unspecified failure mode is not an edge case. It is a latent incident with a date on it.
The output is not a document for its own sake. It is a set of decisions that become code, tests, and alerts.
When to Use
- Designing any feature that crosses a process, network, or service boundary
- Writing a design document or ADR for something with a dependency
- Reviewing code that calls a database, queue, cache, or third-party API
- After an incident, to find the sibling failures that have not happened yet
- Before adding a retry — retries change which failures are possible
NOT for:
- Diagnosing a failure happening right now — that is incident response; this skill is what makes that fast next time
- Deciding retry counts, backoff, and circuit-breaker thresholds — that is a resilience concern; this skill decides whether a retry is even safe
- Making a repeated effect harmless — see
idempotency-and-exactly-once, which owns the "unknown" column this skill keeps producing - Designing telemetry — this skill tells you what must be observable; instrumenting it is separate
Process
1. Draw the boundaries
List every hop where the operation can fail independently of the caller. A boundary is anywhere the failure can happen without your code being told.
POST /checkout
├─ 1. auth service (network)
├─ 2. inventory DB (network + disk)
├─ 3. payment provider (network, third-party, irreversible)
├─ 4. order write (network + disk)
├─ 5. confirmation email (network, third-party, externally visible)
└─ 6. analytics event (network, best-effort)Boundaries you do not list are boundaries you do not handle. In-process function calls are not boundaries; anything with a socket is.
2. Fill the three-outcome table
For every boundary, every call has three outcomes — not two:
| Success | Failure | Unknown | |
| 3. payment provider | record charge | show error, no order | ? |
The unknown column is the one that matters, and the one usually left blank. A timeout, a dropped connection, a process killed mid-write — you do not know whether the operation applied. "Failure" and "unknown" are different states and must produce different behavior.
A blank cell is the finding. Do not move on until every cell has a decision. If the answer is genuinely "we accept the risk", write that down — an accepted risk is a decision; an empty cell is an accident waiting.
For the unknown cell, exactly three answers are legitimate:
- Make it safe to repeat — an idempotency key, so retrying is harmless. Preferred. See
idempotency-and-exactly-once. - Make it detectable — record intent before the call so reconciliation can resolve it later.
- Make it impossible — collapse the boundary, e.g. move the write into the same transaction.
"Retry and hope" is not on the list.
3. Classify the blast radius
For each failure, ask what else breaks:
| Radius | Meaning | Example |
| Contained | This request fails; nothing else notices | One checkout returns 503 |
| Shared-resource | Failure consumes capacity others need | Slow dependency exhausts the connection pool |
| Cascading | Failure propagates to callers of callers | Retry storm takes down a healthy dependency |
| Corrupting | Failure leaves persistent wrong state | Payment captured, order never written |
Contained failures are cheap. Corrupting failures are the expensive ones, and they are the ones a happy-path-first design produces, because the corruption happens in the gap between two boundaries.
Shared-resource is the most under-estimated: a dependency that is slow is often more dangerous than one that is down, because a down dependency fails fast while a slow one holds your threads until nothing is left.
4. Write the degradation ladder
Decide what the system does as conditions worsen, and what triggers each step:
FULL all dependencies healthy
↓ recommendations service p99 > 1s, or error rate > 5%
DEGRADED serve checkout without recommendations
↓ inventory DB read replica lag > 30s
READ-ONLY browse and read; no new orders
↓ payment provider unreachable for > 2 min
FAIL-CLOSED reject checkout with a clear message; do not queue silentlyTwo rules:
- Every transition needs a measurable trigger. "If things look bad" is not a trigger; a threshold on a metric you already emit is.
- Fail-closed is a legitimate destination, and for anything touching money it is usually the right one. Silently queueing work you cannot complete converts an outage into a data problem.
5. Specify the failure's user-visible contract
Decide what the caller sees, and make it honest:
- Which status code, and does it mean "retry this" or "do not retry this"?
- Is the error message diagnosable, or does it assert a cause you have not established?
- Is a partial success reported as success? (It should not be.)
A misleading error costs more than no error. An operator who trusts a wrong message investigates the wrong system.
6. Write the injection plan
Every specified behavior needs a test that proves it. Name the mechanism per boundary:
| Failure | How to inject |
| Dependency down | Point at a closed port |
| Dependency slow | Proxy with added latency; assert your timeout fires first |
| Dependency returns garbage | Stub returning malformed payload |
| Unknown outcome | Kill the process between effect and record |
| Partial write | Fail the second of two writes |
If a failure mode cannot be injected, it cannot be tested, and the specified behavior is a guess. Say so explicitly rather than pretending otherwise.
Common Rationalizations
| Rationalization | Rebuttal |
| "That dependency has never gone down" | Then you have no evidence about what happens when it does. Absence of an incident is not evidence of handling. |
| "The retry will handle it" | Retries handle failure. They amplify unknown into duplicates, and amplify slow into a thundering herd. |
| "We'll add error handling once it works" | The happy path is the easy 20%. Retrofitting failure handling means rediscovering every boundary decision without the design context. |
| "It's an internal service, it's reliable" | Internal networks partition, deploys restart pods mid-request, and internal callers retry more aggressively because it feels free. |
| "If that fails we have bigger problems" | Frequently true and never a design. Write down what happens, even if the answer is "the whole request fails, loudly". |
| "This is over-engineering for our scale" | The table costs an hour. Scale determines how much you invest in each cell, not whether you fill it in. |
| "The framework handles timeouts" | It handles its timeout, with a default you did not choose, which is usually longer than your caller's patience. |
Red Flags
- A design document describing only the successful sequence
- Any external call with no timeout, or a timeout longer than the caller's
- A three-outcome table where the "unknown" column is empty or says "shouldn't happen"
- Retry added to an operation nobody has confirmed is idempotent
except Exception: pass, or a catch that logs and continues into code assuming success- Error messages asserting a cause the code has not established
- A degradation plan whose triggers are human judgement rather than metrics
- Failure paths with no test — especially the partial-write case
- Two writes to different systems with no story for the second one failing
Verification
The analysis is complete when:
- Every boundary is listed, and the list matches what the code actually calls — grep for clients and sockets and compare.
- Every cell in every three-outcome table has a written decision, including accepted risks.
- Every "unknown" cell resolves to safe-to-repeat, detectable, or impossible — never to "retry and hope".
- Every retry has a stated reason to believe the operation is idempotent, with a pointer to what makes it so.
- Every degradation transition has a metric and a threshold that exist today.
- Every specified behavior has a test that injects that failure, and each test fails if you delete the handling it defends.
- Blast radius is classified per failure, and every corrupting failure has either been eliminated or has a detection mechanism.
The exit criterion is the same one that governs this whole pack: if you cannot point at a test that fails when the handling is removed, the behavior is unspecified, whatever the document says.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

