error-handling-patterns
Skill22 starsChanged 2 months agoMITView on GitHub
Designs robust, production-grade error handling — choosing between result types and exceptions, implementing retries with exponential backoff and jitter, applying timeouts and deadlines, adding circuit breakers, and building graceful degradation and fallbacks. Use this skill when writing or reviewing code that calls networks/databases/external APIs, when asked to "make this resilient", "add retry logic", "handle failures", "add timeouts", "handle errors properly", "make this fault tolerant", "add a circuit breaker", "stop swallowing exceptions", or when classifying errors as transient vs permanent and deciding what to retry, fail fast, or degrade.
We split this skill into the rules it gives an agent
11 rules. 0 are common, 9 are distinctive, and 2 are very specific to this project.
0
Common
Most of the 19 edited versions we read keep these.
9
Distinctive
This author's own choices. Worth a look.
2
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Distinctive: line 22
Bound everything: Every remote call gets a timeout.
- Distinctive: line 24
Make retries safe: Only retry operations that are idempotent or made idempotent with an idempotency key.
- Distinctive: line 28
Follow this procedure when designing or reviewing error handling for an operation:
- Distinctive: line 39
Define the degraded behavior: If the dependency is unavailable, what does the user get?
- Distinctive: line 45
Use the result/Either pattern when failures are *expected, common, and part of normal control flow*.
- Very specific: line 84
Use the ready-made implementation in scripts/retry.py (pure stdlib, sync + async, full-jitter backoff, retryable-exception filtering, …
- Very specific: line 90
Prefer deadlines (absolute time) over per-call timeouts in call chains: compute deadline = now + budget once, pass it down, and each call …
- Distinctive: line 107
When a dependency fails, decide the user-visible behavior *in advance*.
- Distinctive: line 114
Never let a non-critical dependency failure break a critical path.
- Distinctive: line 121
Keep error messages actionable: what failed, why, and what the caller can do.
- Distinctive: line 122
Use idempotency keys for all external writes that may be retried.
“Common” is measured: kept by at least 6 in 10 of the 19 edited versions of this file we could read. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Error Handling Patterns
Overview
Robust error handling is about making explicit, deliberate decisions for every failure mode instead of letting failures propagate as unhandled crashes, silent data corruption, or hung requests. This skill provides a decision framework and concrete patterns for: distinguishing recoverable from unrecoverable errors, choosing between result types and exceptions, retrying transient failures safely, bounding latency with timeouts and deadlines, protecting failing dependencies with circuit breakers, and degrading gracefully when a dependency is down.
Keywords: error handling, exceptions, result type, Either, retry, exponential backoff, jitter, timeout, deadline, circuit breaker, bulkhead, idempotency, graceful degradation, fallback, fail fast, fault tolerance, resilience, transient errors, dead letter queue.
Apply this skill whenever code crosses a trust or failure boundary: network calls, database queries, file I/O, external APIs, message queues, subprocess execution, or any operation that can fail in ways the caller must handle.
Core Principles
- Every error has exactly one owner. At each layer, decide: handle it here, transform and rethrow, or let it propagate. Never silently swallow.
- Classify before you react. A failure is either transient (retry may succeed) or permanent (retry is futile). The classification drives every other decision. See
references/error-taxonomy.md. - Fail fast on programmer errors, recover from operational errors. Bugs (null deref, bad invariants) should crash loudly. Operational failures (timeout, 503, connection reset) should be handled.
- Bound everything. Every remote call gets a timeout. Every retry loop gets a max attempt count and a deadline. Unbounded waits and unbounded retries are outages waiting to happen.
- Preserve context. Wrap errors with what you were doing, but keep the original cause chain. Never throw away stack traces or root causes.
- Make retries safe. Only retry operations that are idempotent or made idempotent with an idempotency key. Retrying a non-idempotent write can double-charge a customer.
Workflow
Follow this procedure when designing or reviewing error handling for an operation:
- Enumerate failure modes. List what can go wrong: timeout, connection refused, 4xx, 5xx, malformed response, partial write, auth expiry, rate limit, disk full, deserialization error. Use the checklist in
references/error-taxonomy.md. - Classify each failure as transient (retryable), permanent (non-retryable), or ambiguous (unknown outcome — treat writes as possibly-applied).
- Choose the error-signaling style for this layer using the decision table below: exceptions vs result type. Be consistent within a module.
- Decide the reaction per class:
- Transient → retry with backoff + jitter, bounded by attempts and deadline.
- Permanent → fail fast, surface a clear, actionable error to the caller.
- Ambiguous → only retry if idempotent; otherwise reconcile or fail and alert.
- Add a timeout/deadline to every remote call. Propagate a deadline across call chains so total latency is bounded.
- Add a circuit breaker around dependencies that can fail in bulk, to stop hammering a downed service and to fail fast while it recovers.
- Define the degraded behavior. If the dependency is unavailable, what does the user get? Cached data, a default, reduced functionality, or a clean error? Decide explicitly.
- Wrap and log with context at the boundary where you have the most information; avoid double-logging the same error at every layer.
- Test the failure paths. Inject timeouts, 500s, and connection drops. Verify retries, backoff, breaker trips, and fallbacks. Failure paths are the least-tested and most important code.
Decision Framework: Result vs Exception
Use the result/Either pattern when failures are expected, common, and part of normal control flow; use exceptions for exceptional, rare, or non-local conditions.
| Use a Result/Either type when… | Use an Exception when… |
| Failure is a normal outcome (parse, validate, lookup-miss) | Failure is unexpected / a violated invariant |
| The caller must always handle the failure | The error should unwind many stack frames |
| You want failures visible in the type signature | Handling far from the throw site is acceptable |
| Performance-sensitive hot paths (no stack unwinding) | Constructors / places that can't return values |
| Functional / explicit-flow codebases | Idiomatic in the language (Python, Ruby, Java checked) |
Rules of thumb:
- Don't use exceptions for control flow that runs on every request.
- Don't return
null/-1/falseto mean "error" — that loses the reason. Return a typed result. - Don't catch-all-and-continue. A bare
except: pass/catch (e) {}is the single most common resilience bug. - One style per layer. Translate at boundaries (e.g., catch low-level exceptions, return domain results from a service).
See references/patterns-by-language.md for idiomatic result/exception patterns in Python, TypeScript, Go, Rust, and Java.
Retries: Backoff and Jitter
Retries turn transient blips into successes — but naive retries cause retry storms and thundering herds that amplify outages. Rules:
- Only retry idempotent or idempotency-keyed operations. GET/PUT/DELETE are typically safe; POST usually is not unless you send an idempotency key.
- Only retry transient/retryable errors (timeouts, 429, 502/503/504, connection resets). Never retry 400/401/403/404/422 or validation errors.
- Use exponential backoff with jitter, not fixed delays. Full jitter (
sleep = random(0, base * 2^attempt)) is the recommended default; it decorrelates clients. - Cap the delay (e.g., max 30s) and the attempts (e.g., 3–5), and enforce an overall deadline.
- Respect
Retry-Afterheaders from the server (429/503). The server knows best. - Add a circuit breaker so retries stop entirely when a dependency is clearly down.
- Budget retries. Track a retry budget (e.g., retries ≤ 10% of requests) so retries can't dominate traffic.
Backoff comparison (base = 100ms):
| Strategy | Formula | Problem it solves / causes |
| Fixed | delay = base | Simple; causes synchronized retry storms |
| Exponential, no jitter | base * 2^n | Backs off, but clients stay synchronized |
| Exponential + full jitter | random(0, base * 2^n) | Recommended. Decorrelates clients |
| Decorrelated jitter | min(cap, random(base, prev*3)) | Good for self-clocking clients |
Use the ready-made implementation in scripts/retry.py (pure stdlib, sync + async, full-jitter backoff, retryable-exception filtering, deadline support). See examples/resilient-api-call.md for a full worked example combining retry + timeout + circuit breaker + fallback.
Timeouts and Deadlines
- Every network/DB/IO call must have a timeout. The default of "infinite" is wrong.
- Set connect and read timeouts separately; connect should be short (1–3s).
- Prefer deadlines (absolute time) over per-call timeouts in call chains: compute
deadline = now + budgetonce, pass it down, and each call usesremaining = deadline - now. This bounds total latency, not per-hop. - A timeout is an ambiguous failure for writes: the operation may have succeeded. Make writes idempotent so a retry is safe.
- Choose timeouts from latency data: roughly p99 + margin, not a guessed round number. Too-tight timeouts cause spurious failures; too-loose ones cause cascading slowness.
Circuit Breakers and Bulkheads
A circuit breaker stops calls to a failing dependency to let it recover and to fail fast:
- Closed → calls flow; count failures.
- Open → after the failure threshold trips, reject immediately (fail fast / fall back) for a cooldown.
- Half-open → after cooldown, allow a few trial calls; success closes, failure re-opens.
A bulkhead isolates resources (separate connection pools / thread pools / concurrency limits per dependency) so one slow dependency can't exhaust shared capacity and take down everything.
scripts/retry.py includes a small thread-safe CircuitBreaker. See references/resilience-patterns.md for thresholds, tuning, and how breakers, bulkheads, retries, and timeouts compose.
Graceful Degradation
When a dependency fails, decide the user-visible behavior in advance. Options, from best to worst UX:
- Serve from cache (possibly stale) with a freshness indicator.
- Return a sensible default / reduced feature (e.g., hide recommendations, keep checkout working).
- Queue for later (write to a durable queue / outbox, process when healthy).
- Partial response — return what succeeded, mark what failed.
- Clean, actionable error — never a stack trace or hung spinner.
Never let a non-critical dependency failure break a critical path. Recommendation service down? The product page must still render.
Best Practices
- Wrap-and-chain errors with context: include the operation, key identifiers, and the original cause (
raise X from err,fmt.Errorf("...: %w", err),new Error(msg, { cause })). - Log an error once, at the boundary that has full context. Pass typed errors up; don't log-and-rethrow at every frame.
- Make failures observable: emit metrics (error rate, retry count, breaker state, timeout count) and structured logs with correlation IDs.
- Keep error messages actionable: what failed, why, and what the caller can do.
- Use idempotency keys for all external writes that may be retried.
- Centralize policy (timeouts, retry config, breaker thresholds) as configuration, not scattered magic numbers.
- Distinguish user-facing messages (safe, generic) from internal details (full diagnostics) — don't leak internals.
- Clean up resources on every path (
finally,defer, context managers, RAII).
Common Pitfalls
- Swallowing exceptions (
except: pass, emptycatch) — hides bugs and corrupts state. At minimum, log and rethrow. - Retrying non-idempotent writes — double charges, duplicate orders. Use idempotency keys.
- Retrying permanent errors (400/401/404) — wastes time, hammers the server, delays the real failure.
- No jitter — synchronized clients create retry storms that DDoS your own backend.
- Unbounded retries / no deadline — turns a blip into a hang.
- Missing timeouts — one slow dependency exhausts all threads/connections; cascading failure.
- Catching
Exception/Throwabletoo broadly — masksKeyboardInterrupt,OutOfMemory, programmer errors. Catch specific types. - Losing the cause chain — re-raising a new error without
from/cause/%wdestroys the root cause. - Double logging — the same error logged at five layers makes triage impossible.
- Treating timeout as definite failure for writes — leads to lost updates or unexpected duplicates.
- No circuit breaker — clients keep retrying a downed service, preventing its recovery.
Bundled Resources
references/error-taxonomy.md— failure-mode checklist; transient vs permanent vs ambiguous classification with HTTP/DB/network specifics.references/patterns-by-language.md— idiomatic result/exception, retry, and timeout patterns in Python, TypeScript, Go, Rust, Java.references/resilience-patterns.md— deep dive on backoff math, circuit breakers, bulkheads, idempotency, deadlines, retry budgets, and how they compose.scripts/retry.py— runnable, stdlib-only retry (sync + async) with full-jitter backoff, retryable-error filtering, deadlines, and a circuit breaker.examples/resilient-api-call.md— full worked example: a flaky API client with timeout + retry + breaker + cache fallback, and how it behaves under each failure mode.templates/error-handling-review.md— fill-in checklist/template for reviewing a code change's error handling.
Other versions21 projects use a version of this.
Word for word · 2
- JayRHa/agent-skills★ 12100% same
- JayRHa/AgentSkills★ 12100% same
Edited · 0
None found.
Wrote their own version · 19
- The-AIOS/aios★ 93same name
- farming-labs/fm-skills★ 35same name
- JuankCadavid/akili-specs★ 9same name
- xfstudio/skills★ 9same name
- KhaledSaeed18/dotclaude★ 5same name
- ai4brands-design/claude-skills★ 2same name
- anthuanvasquez/agents-skills★ 2same name
- CodeWithBehnam/cc-docs★ 2same name
- roshankodi/agent-skills★ 2same name
- codecharmhq/claude-code-skills★ 1same name
9 more not listed
How matches are found. Word for word: the exact same text. Edited: shares passages with this file (“99% same” means 99% of its five-word phrases also appear here; 10% or more counts). Their own version: similar meaning, or, weakest, the same name. We can’t tell which project had it first. Projects are counted among the 200 most-starred candidates, and mentions are links to the repository we found, up to 50 per site, so both counts are a floor.
In this repository194 other files it gives its agents.
- 0-templateSkill
- ab-test-analyzerSkill
- academy-guideSkill
- add-to-my-skillsSkill
- adr-authorSkill
- ai-tools-setupSkill
- algorithmic-artSkill
- api-and-interface-designSkill
- api-designerSkill
- api-docs-writerSkill
- apple-store-submitSkill
- aws-cost-optimizerSkill
- bash-script-hardeningSkill
- blog-post-writerSkill
- book-summarizerSkill
- branch-cleanupSkill
- brand-copy-landingSkill
- brand-copySkill
- brand-designSkill
- brand-guidelinesSkill
- browser-testing-with-devtoolsSkill
- canvas-designSkill
- changelog-generatorSkill
- changelog-keeperSkill
- chart-chooserSkill
- ci-cd-and-automationSkill
- ci-fix-loopSkill
- claude-apiSkill
- cloudflare-block-countriesSkill
- coderabbitloopSkill
- code-review-and-qualitySkill
- code-reviewerSkill
- code-simplificationSkill
- codexloopSkill
- comms-notionSkill
- comms-slackSkill
- comms-whatsappSkill
- competitive-analysisSkill
- concept-explainerSkill
- constraint-driven-developmentSkill
- context-engineeringSkill
- context-repoSkill
- cover-letter-writerSkill
- crash-courseSkill
- cron-schedulerSkill
- data-digestSkill
- data-pipeline-architectSkill
- debug-detectiveSkill
- debugging-and-error-recoverySkill
- decision-matrixSkill
- dependabot-triageSkill
- dependency-upgraderSkill
- deprecation-and-migrationSkill
- dev-instantdbSkill
- discernment-nudgeSkill
- doc-coauthoringSkill
- dockerfile-proSkill
- docs-index-keeperSkill
- documentation-and-adrsSkill
- docxSkill
- The first 60 shown.
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.

