agentleFS
rlaoperlaope/ultraprompt/skills/failure-mode-enumeration/SKILL.md

failure-mode-enumeration

Skill35 starsChanged 16 days agoMITView on GitHub

Systematically enumerate how code can break — boundaries, concurrency, partial failure, malicious input, time, resource exhaustion — before writing defenses, then convert the list into tests and guards while pruning paranoia that does not pay rent.

The file

We split this skill into the rules it gives an agent

7 rules. About 0 are common, 6 are distinctive, and 1 is very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈6

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 1

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Distinctive:

    Write the unit's contract in one line: inputs, outputs, and the invariants that must hold after it runs.

    line 22
  2. Distinctive:

    Run the pre-mortem: Ask: "It is 3 months from now and this component caused an incident.

    line 37
  3. Very specific:

    Every await, network call, and disk write is a point where the process can die: ask "what state is left if we stop exactly here?" at each …

    line 65
  4. Distinctive:

    If an operation has N steps with side effects, there are N+1 crash points.

    line 66
  5. Distinctive:

    If a queue, buffer, or cache has no explicit size bound, its bound is total process memory — write the bound or accept the OOM.

    line 70
  6. Distinctive:

    Every timeout must be shorter than the caller's timeout, or the caller gives up first and your result is delivered to nobody.

    line 71
  7. Distinctive:

    When input comes from outside the process, validate at the boundary once and pass typed/parsed values inward — do not re-validate at every …

    line 74

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

Status: v0.1 baseline draft — derived from the case catalog and axis definitions; awaiting trace evidence from Fable 5.1 case runs.

When to apply

  • Writing any code that crosses a trust or process boundary: network calls, disk I/O, user input, IPC, subprocess spawning.
  • Designing a system where partial failure is possible: queues, retries, distributed transactions, multi-step writes.
  • Implementing a parser, protocol handler, or format decoder that will receive arbitrary bytes.
  • Adding concurrency: shared state, locks, async cancellation, anything with more than one thread of execution.
  • Reviewing code before shipping when the cost of a production failure is high (payments, data loss, security).
  • The user asks "what could go wrong here?" or wants tests hardened against edge cases.

Core loop

  1. Fix the unit of analysis first. Name the exact function, endpoint, or state machine you are enumerating against. Enumerating "the system" produces vague worry; enumerating consume_message() produces a checklist. Write the unit's contract in one line: inputs, outputs, and the invariants that must hold after it runs.
  2. Run the standard failure taxonomy against it, category by category. Do not brainstorm freely — walk the fixed list so nothing is skipped:
    • Boundary values: zero, one, max, max+1, negative, empty string/list/map, exactly-at-limit.
    • Huge inputs: 10^6x the expected size — does memory, latency, or a downstream limit blow up first?
    • Malformed/malicious input: wrong type, truncated bytes, injection payloads, adversarially crafted values (lengths that overflow, names that collide, nesting that recurses).
    • Concurrency interleavings: two callers at once; read-modify-write races; check-then-act gaps; reentrancy; cancellation arriving mid-operation.
    • Partial failure: a network call succeeds but the response is lost; a disk write half-completes; the process dies between step 2 and step 3 of a 3-step operation; a downstream returns 500 after the side effect already committed.
    • Time: clock skew between machines, DST transitions, leap seconds, events arriving out of order, timeouts firing during success, retries after the original eventually succeeded.
    • Resource exhaustion: connection pool empty, file descriptors gone, disk full, OOM, thread pool saturated, queue growing without bound.
  3. Run the pre-mortem. Ask: "It is 3 months from now and this component caused an incident. What is the postmortem's root cause?" Write the 2-3 most plausible answers before continuing. This surfaces systemic failures — config drift, a dependency upgrade, 10x load growth — that the taxonomy's input-level sweep misses.
  4. Classify each enumerated mode by blast radius: (a) wrong answer returned silently, (b) crash or error surfaced loudly, (c) data corrupted or lost, (d) resource leaked slowly. Silent-wrong-answer and corruption modes get priority; loud crashes are already half-defended because someone will notice them.
  5. Price each mode: likelihood × blast radius × cost to defend. Kill the modes that fail this test explicitly — write "not defending against X because Y" in a comment or design note. Paranoia that does not pay rent becomes code that other people must read, maintain, and debug false positives from.
  6. Convert every survivor into a concrete artifact: a test that reproduces the failure (kill -9 mid-write, inject the 0-length input, spawn two concurrent callers), OR a guard in code (timeout, size bound, idempotency key, checksum), OR both for corruption-class modes. An enumerated mode with no test and no guard is a wish, not a defense.
  7. Verify each guard by triggering the failure it defends against. A guard that has never fired is unverified. Force the pool to exhaust, feed the truncated bytes, kill the process at the marked line. If a mode genuinely cannot be triggered in a test environment, downgrade your confidence in that guard and say so in the report.
  8. Re-run steps 2-3 after implementation. The code you actually wrote has failure modes the design did not: a new allocation, a new lock, a new syscall, new retry state. Diff the final code against the original enumeration and add the modes the implementation introduced.

Heuristics

  • Every await, network call, and disk write is a point where the process can die: ask "what state is left if we stop exactly here?" at each one.
  • If an operation has N steps with side effects, there are N+1 crash points; the operation must be idempotent or resumable, or it will corrupt state eventually.
  • Any check-then-act sequence (if exists: update) is a race unless the check and the act are atomic — assume two callers hit the gap on day one of real traffic.
  • Empty collection, single element, and exactly-at-capacity find more bugs per minute than random fuzzing; test those three before anything else.
  • Retries turn one failure mode into two: the original failure, and the duplicate side effect when the "failed" call actually succeeded. Never add a retry without an idempotency story.
  • If a queue, buffer, or cache has no explicit size bound, its bound is total process memory — write the bound or accept the OOM.
  • Every timeout must be shorter than the caller's timeout, or the caller gives up first and your result is delivered to nobody.
  • Pruning budget rule: if the failure requires 2+ independent rare events AND its blast radius is loud-crash (not corruption), skip the guard and leave a comment instead.
  • Timestamps from different machines are not ordered; if correctness depends on order, use a logical clock or a single writer.
  • When input comes from outside the process, validate at the boundary once and pass typed/parsed values inward — do not re-validate at every layer.

Anti-patterns

  • Defense before enumeration. Sprinkling null checks and try/catch while writing code, without a systematic list — produces uneven armor: triple-checked trivia next to unguarded corruption paths. → Corrective: enumerate first against the fixed taxonomy, then defend the priced list.
  • Happy-path tests labeled as edge-case tests. Testing "empty input returns empty output" but never "process dies between WAL append and index update." → Corrective: for each corruption-class mode, write the test that actually induces the failure (fault injection, kill -9, concurrent load), not a test adjacent to it.
  • Uniform paranoia. Guarding a local CLI tool's config parser as hard as a public API's request parser. Defenses have carrying cost — reading, maintaining, and false-positive firing. → Corrective: price every mode (step 5), delete guards for modes you explicitly declined, record the decision.
  • Catch-and-continue as a defense. Wrapping the whole operation in a broad exception handler that logs and proceeds converts loud crashes into silent wrong answers — the worst blast-radius class. → Corrective: handle only the specific modes you enumerated; let everything else crash loudly.
  • Enumerating inputs but not time and interleavings. Input-space sweeps feel complete but miss the races and ordering bugs that dominate production incidents. → Corrective: the concurrency, partial-failure, and time categories are mandatory passes, not optional extras.
  • The list that never becomes code. A thorough failure-mode document with zero corresponding tests or guards. → Corrective: step 6 is the deliverable — each surviving mode maps to a named test or a named guard, one-to-one.

Worked example

Illustrative construction, not a recorded run.

Task: implement a message-queue consumer with retry and DLQ.

  • Agent fixes the unit: consume(msg) -> ack|nack, invariant: "every message is processed at least once and side effects are not duplicated on redelivery."
  • Agent walks the taxonomy against consume:
    • Boundary: 0-byte body; body exactly at the broker's max frame size.
    • Huge: 50MB body when the handler assumes JSON fits in memory.
    • Malformed/malicious: non-UTF8 bytes; valid JSON with wrong schema; a poison message that deterministically crashes the handler.
    • Interleavings: broker redelivers while the first delivery is still processing (visibility timeout shorter than handler runtime).
    • Partial failure: handler writes to DB, then the process dies before ack → redelivery duplicates the DB write.
    • Time: retry backoff timer fires during a broker reconnect; messages processed out of order after redelivery.
    • Exhaustion: unbounded in-flight prefetch fills memory when the downstream DB slows.
  • Agent runs the pre-mortem: "3 months later, incident: one poison message crash-looped the consumer, queue backed up 6 hours." Second answer: "duplicate payment rows after a deploy killed pods mid-ack."
  • Agent classifies: duplicate-DB-write and poison-crash-loop are corruption/availability class → priority. Non-UTF8 body is loud-crash class and cheap to guard → keep. Leap-second handling → priced out; comment written: "not defending; broker timestamps unused for ordering."
  • Agent converts survivors:
    • Idempotency key on the DB write (guard) + a test that delivers the same message twice.
    • Retry counter with DLQ routing after 3 attempts (guard) + a test injecting a handler that always throws.
    • Prefetch bound of 32 (guard) + a test with a 10-second-sleep handler asserting memory stays flat.
  • Agent verifies by triggering: runs the duplicate-delivery test; kills the consumer with SIGKILL between the DB commit and the ack; observes redelivery; asserts exactly one row exists. The poison test shows the message lands in the DLQ on attempt 4.
  • Post-implementation re-sweep: the retry counter itself is new state — where does it live if the process dies? Agent observes it was in-memory → moves it into the message header (broker-side), closing a mode the design phase could not have seen because the counter did not exist yet.

Trace evidence

CaseSessionWhat it showed
awaiting case runs——

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.