agentleFS
abhisheksharma2411abhisheksharma2411/distributed-systems-skills/skills/failure-mode-analysis/SKILL.md

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.

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
  1. Distinctive:

    After an incident, to find the sibling failures that have not happened yet

    line 21
  2. Distinctive:

    Before adding a retry — retries change which failures are possible

    line 22
  3. Distinctive:

    A blank cell is the finding: Do not move on until every cell has a decision.

    line 58
  4. Distinctive:

    Every specified behavior needs a test that proves it.

    line 114
  5. Distinctive:

    If a failure mode cannot be injected, it cannot be tested, and the specified behavior is a guess.

    line 124

≈ “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.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

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:

SuccessFailureUnknown
3. payment providerrecord chargeshow 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:

  1. Make it safe to repeat — an idempotency key, so retrying is harmless. Preferred. See idempotency-and-exactly-once.
  2. Make it detectable — record intent before the call so reconciliation can resolve it later.
  3. 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:

RadiusMeaningExample
ContainedThis request fails; nothing else noticesOne checkout returns 503
Shared-resourceFailure consumes capacity others needSlow dependency exhausts the connection pool
CascadingFailure propagates to callers of callersRetry storm takes down a healthy dependency
CorruptingFailure leaves persistent wrong statePayment 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 silently

Two 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:

FailureHow to inject
Dependency downPoint at a closed port
Dependency slowProxy with added latency; assert your timeout fires first
Dependency returns garbageStub returning malformed payload
Unknown outcomeKill the process between effect and record
Partial writeFail 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

RationalizationRebuttal
"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:

  1. Every boundary is listed, and the list matches what the code actually calls — grep for clients and sockets and compare.
  2. Every cell in every three-outcome table has a written decision, including accepted risks.
  3. Every "unknown" cell resolves to safe-to-repeat, detectable, or impossible — never to "retry and hope".
  4. Every retry has a stated reason to believe the operation is idempotent, with a pointer to what makes it so.
  5. Every degradation transition has a metric and a threshold that exist today.
  6. Every specified behavior has a test that injects that failure, and each test fails if you delete the handling it defends.
  7. 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.

Download now (ZIP)

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
What did you use it for? e.g. Reviewing pull requests in a Rails app

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.