---
name: handle-errors-gracefully
description: Adds sensible error handling to an app - separate expected errors from bugs, show friendly messages to users, log the details with context, retry only what is safe, and stop one failing service from taking the whole app down. Use when the app crashes on bad input, users see raw stack traces, or errors are swallowed and nothing gets logged.
license: MIT
metadata:
  category: backend
  tags: "errors,quality,api"
  author: johnny-bravo
  version: "1.0"
  updated: "2026-11-02"
  status: published
  aliases: "error handling, try catch, exceptions, retries"
  source_url: "https://github.com/wshobson/agents/tree/7e2fb559cf4b824b27f1fec49fe88f7511066d26/plugins/developer-essentials/skills/error-handling-patterns"
  source_license: MIT
  copyright: "Copyright (c) 2024 Seth Hobson"
  original_author: "Seth Hobson (wshobson/agents, error-handling-patterns)"
  modified: "Adapted by TheSkillVault: rewritten as a beginner checklist with original examples; dropped the multi-language pattern catalogue; added secret-safe logging, gotchas and done-when."
  imported_on: "2026-11-02"
---

# Handle errors gracefully

Errors will happen. The goal is that users see a clear message, you see the details, and one failure does not break everything.

## When to use
- Users see stack traces, blank screens or "undefined" messages.
- Errors are caught and ignored, so bugs go unnoticed.
- Calls to a payment, email or AI provider fail now and then.

## Steps
1. Sort errors into two kinds: expected (bad input, not found, not allowed) and unexpected (bugs, outages).
2. For expected errors, return a clear message and the right status code. Do not log them as alarms.
3. For unexpected errors, show a generic message ("Something went wrong, try again") and log the full detail.
4. Add one top-level handler (Express error middleware, a Next.js `error.tsx`, a FastAPI exception handler) so nothing escapes raw.
5. Log with context: route, user id, request id, and the error with its stack.
6. Never swallow errors. An empty `catch {}` must at least log or re-throw.
7. Retry only safe, temporary failures (timeouts, 429, 503) with backoff, and cap the attempts.
8. Set timeouts on every outside call so one slow service cannot hang a request.
9. Show partial results where possible, such as the page without the broken widget.
10. Send unexpected errors to a tracker such as Sentry, and check it after each release.

```ts
try {
  await sendReceipt(order);
} catch (err) {
  logger.error({ err, orderId: order.id }, "receipt email failed");
  // order is still saved; the receipt can be retried later
}
```

## Gotchas
- Never put passwords, tokens, API keys or full card numbers in logs or error messages.
- Do not retry a payment or anything else that is not safe to repeat unless it uses an idempotency key.
- Error messages that say "user not found" vs "wrong password" help attackers. Use one message for failed sign-ins.
- Catching too high up hides where the problem started. Keep the original error as the `cause`.

## Portability
Works with any language. If you use Python, Go or Rust instead of JavaScript, the same split of expected and unexpected errors applies with that language's equivalent of try/catch or result types.

## Example prompt
"My API crashes and shows stack traces when something fails. Add proper error handling, friendly messages and logging."

## Done when
- No route can return a raw stack trace to a user.
- Unexpected errors are logged with context and no secrets.
- Outside calls have timeouts, and only safe calls are retried.

---
Adapted from "error-handling-patterns" in wshobson/agents (MIT, Copyright (c) 2024 Seth Hobson). See LICENSE.txt.
