Skip to content
Development
Skill

/error-handling-patterns

How to handle errors explicitly and consistently across an app — validate at boundaries, classify operational vs programmer errors, add context while propagating, retry transient failures with backoff, and never swallow. Covers JS/Python/Go/Rust patterns with runnable checks.

From plugin
skills
917 skills54 agents

Claude Code

$ npx -y skills add vanara-agents/skills --skill error-handling-patterns --agent claude-code

SKILL.md

error-handling-patterns.SKILL.md
name: error-handling-patterns
description: How to handle errors explicitly and consistently across an app — validate at boundaries, classify operational vs programmer errors, add context while propagating, retry transient failures with backoff, and never swallow. Covers JS/Python/Go/Rust patterns with runnable checks.
type: skill
version: 2.0.0
updated: 2026-06-29

Error Handling Patterns

Errors are part of the contract, not an afterthought. A robust system **handles errors where it can act, propagates them where it can't, and never lets a failure vanish silently**. This skill is the deep reference: the decisions, the trade-offs, and the anti-patterns. Heavy detail lives in `references/`, copy-paste material in `examples/`, and a runnable linter for swallowed errors in `scripts/`.

Mental model

Every error sits on two axes that decide what you do with it:

| Axis | Question | Consequence | |---|---|---| | **Operational vs programmer** | Is this an expected runtime condition (network down, bad input) or a bug (null deref, broken invariant)? | Operational → recover/retry/surface. Programmer → fail fast, let it crash, fix the code. | | **Recoverable vs fatal** | Can the caller do something useful about it here? | Yes → handle locally. No → add context and propagate. |

Classifying wrong is the root cause of most bad error handling: retrying a programmer bug forever, or crashing the process over a single bad HTTP request. The full taxonomy is in [`references/error-taxonomy.md`](references/error-taxonomy.md).

The five rules

1. **Validate at the boundary.** Reject bad input early with a clear, specific message (fail fast). Never trust data crossing a trust boundary — HTTP bodies, env vars, file contents, API responses. 2. **Classify the error.** Operational errors are values you handle; programmer errors are bugs you surface loudly. Don't `catch` a `TypeError` to "be safe". 3. **Add context as you propagate.** Wrap with *what you were doing* and preserve the original cause and stack (`new Error(msg, { cause })` in JS, `fmt.Errorf("...: %w", err)` in Go). 4. **One owner for user-facing messaging.** Translate to a friendly message at the edge (the HTTP layer, the UI). Keep full detail in server-side logs only — never leak stack traces to users. 5. **Never swallow.** An empty `catch {}`, a bare `except: pass`, or a `.catch(() => {})` is a defect. Run `scripts/lint-empty-catch.mjs` in CI to catch these automatically.

Adding context without losing the cause

The single most valuable habit: wrap errors with context while chaining the original, so the final log shows the full causal trail from the low-level failure up to the request that triggered it.

// Node 16.9+ supports the standard `cause` option on Error.
async function placeOrder(userId, cart) {
  try {
    const order = await db.orders.insert({ userId, items: cart.items });
    return order;
  } catch (err) {
    // Wrap: keep the DB error as `cause`, add the business context.
    throw new Error(`failed to place order for user ${userId}`, { cause: err });
  }
}
# Python's `raise ... from` preserves the chain (shown as "The above exception
# was the direct cause of the following exception" in the traceback).
def place_order(user_id, cart):
    try:
        return db.orders.insert(user_id=user_id, items=cart.items)
    except DBError as err:
        raise OrderError(f"failed to place order for user {user_id}") from err

Language-by-language patterns (try/catch vs `Result`/`Either` vs panics) are in [`references/language-patterns.md`](references/language-patterns.md).

Retrying transient failures

Only **operational, transient, idempotent** failures are safe to retry — a 503 or a connection reset, not a 400 or a `NullPointerException`. Retry with **exponential backoff plus full jitter** to avoid synchronized retry storms (the "thundering herd"), and always cap attempts and total time.

async function withRetry(fn, { retries = 4, baseMs = 100, isRetryable } = {}) {
  let attempt = 0;
  for (;;) {
    try {
      return await fn();
    } catch (err) {
      attempt++;
      if (attempt > retries || (isRetryable && !isRetryable(err))) throw err;
      const backoff = baseMs * 2 ** (attempt - 1);
      const jittered = Math.random() * backoff; // full jitter
      await new Promise((r) => setTimeout(r, jittered));
    }
  }
}

When NOT to retry, circuit breakers, and budgets are covered in [`references/retry-and-backoff.md`](references/retry-and-backoff.md).

Common pitfalls (anti-patterns)

  • **Swallowing errors** — empty `catch {}`, `except: pass`, `.catch(() => {})`, or `if err != nil {}` in

Go. The failure disappears and you debug blind. This is the #1 defect; lint for it.

  • **Catch-log-rethrow-wrapped duplication** — logging *and* rethrowing at every layer produces the same

error logged ten times. Log once, at the boundary that owns the response.

  • **Catching too broadly** — `except Exception` or `catch (e)` that also eats `KeyboardInterrupt`,

programmer bugs, or `OutOfMemory`. Catch the narrowest type you can actually handle.

  • **Returning a sentinel instead of erroring** — returning `null`/`-1`/`{}` on failure forces every

caller to remember the magic check; most won't. Prefer throwing or a `Result` type.

  • **Leaking internals to users** — returning raw stack traces or SQL errors is both bad UX and a security

leak (reveals schema, file paths, library versions).

  • **Retrying non-idempotent or non-transient operations** — retrying a `POST /charge` on timeout can

double-charge; retrying a `400` just wastes time. See the retry reference.

  • **Losing the original cause** — `throw new Error("save failed")` without `{ cause }` discards the stack

that tells you *why*.

When NOT to use / trade-offs

  • **Don't catch programmer errors to keep running.** A bug that corrupted state should crash the process

(let a supervisor like systemd/k8s/PM2 restart it cl

Read more
@vanara-agents
Ships withskills

🐒 Free agents, skills & packs for Claude Code One subscription. An army of Claude Code agents. 30 production-grade agents, skills, and packs for Claude Code — free, Apache-2.0, install with one command.

917 skills54 agents
Get the whole plugin

Stars on GitHub

Momentum

stars in total
9
Star history of vanara-agents/skills on GitHub, a chart by star-history.com

Other skills on skills.

api-pagination

api-pagination

Implement correct, fast API pagination — cursor vs offset trade-offs, opaque cursor encoding, stable sort keys, page-size limits,…

caching-strategies

caching-strategies

Deep reference for caching — what to cache, cache-aside vs read/write-through/write-behind, TTLs with jitter, eviction…

conventional-commits

conventional-commits

Write Conventional Commits — the type(scope)!: subject + body + footer spec — so history is readable and changelogs and SemVer…

database-migrations

database-migrations

How to write safe, reversible, zero-downtime database schema migrations — additive-first changes, the expand/migrate/contract…

owasp-top10

owasp-top10

A deep prevention reference for the OWASP Top 10 web risks — broken access control, injection, crypto failures, insecure design,…

More skills from other plugins.

brainstorming
Auto-invoked

brainstorming

You MUST use this before any creative work - creating features, building components, adding functionality, modifying behavior, or…

@obra@obraInstall Skill
ask-matt

ask-matt

Ask which skill or flow fits your situation. A router over the skills in this repo.

skills13d309k
@mattpocock@mattpocockInstall Skill
accessibility
Auto-invoked

accessibility

Design, implement, and audit accessible UI to WCAG 2.2 Level AA across Web, iOS, and Android — semantic ARIA roles and labels,…

ecc2w293k
@affaan-m@affaan-mInstall Skill
compiler-commit

compiler-commit

Use when you want to verify compiler changes and commit with the correct convention. Runs tests, lint, and format, then commits…

react4d252k
@facebook@facebookInstall Skill

Questions about error-handling-patterns.

Which coding agents can use error-handling-patterns?
error-handling-patterns installs in Claude Code, Codex, Cursor, Gemini CLI, GitHub Copilot, OpenCode and Windsurf with the skills CLI, using each agent's flag shown on this page. Flowy's auto-invocation, which fires a skill when your prompt matches, runs in Claude Code only.