agentsclimarketplace

Error handling

Skill felixhennequin-gif/claude-code-config-template/cli/template-files/claude/skills/core/error-handling

Production-ready AI config template for Claude Code. CLAUDE.md, agents, skills, hooks, routines, and commands — based on analysis of 55+ open-source repos (Supabase, Bitwarden, Vercel, Cloudflare, OpenAI).

Install
npx -y skills add felixhennequin-gif/claude-code-config-template --skill error-handling

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • 0 stars0 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

What its author says it does

Copied from the file, not written here

Error handling patterns for HTTP services. Activates when writing error handling code, creating error classes, working with try/catch blocks, or implementing error middleware in an HTTP backend (Express, FastAPI, Flask, Django, Gin, Axum, etc.).

SKILL.md

5.4 KB, as published. Nobody here has run it

Error handling (HTTP services)

Four rules for HTTP backends, with parallel JavaScript (Express) and Python (FastAPI) examples.

Scope. This skill covers HTTP request/response error handling — the centralized-handler pattern, typed errors, and status-code mapping. It deliberately does not cover gRPC, GraphQL, message queues, or CLI tools; those have different boundary semantics (DLQs, error extensions, exit codes) and deserve their own skill rather than a handwave at the end of this one. Rules 1–3 are portable in spirit, but the examples and Rule 4's handler shape assume an HTTP framework.

1. Fail loudly at the boundary, silently never

Errors should propagate up to the layer that can handle them meaningfully. Never swallow an error silently. The right default in intermediate layers is no try/catch at all — not a rethrow. Wrapping a call only to rethrow is a no-op that linters (ESLint no-useless-catch) already flag.

// BAD — error is gone, caller thinks it succeeded
try {
  await doSomething();
} catch (e) {}

// GOOD — let it propagate naturally. No try/catch needed here.
// Errors bubble up to the boundary (e.g. Express 5 error middleware,
// or an outer try/catch in the entry point) where they're logged once.
await doSomething();

2. Fix at the root, not the symptom

If a function can return null/undefined/error, fix the function — don't add null checks everywhere it's called. The caller shouldn't have to defend against bad state that the callee should never produce.

3. Typed errors over string messages

Use typed error classes so callers can distinguish error types programmatically, not by parsing strings.

// BAD
throw new Error('Not found');

// GOOD
class NotFoundError extends AppError {
  constructor(resource) {
    super(`${resource} not found`, 404);
  }
}
throw new NotFoundError('User');
# Python equivalent — same shape, same rule:
class AppError(Exception):
    def __init__(self, message: str, status: int = 500):
        super().__init__(message)
        self.status = status

class NotFoundError(AppError):
    def __init__(self, resource: str):
        super().__init__(f"{resource} not found", 404)

raise NotFoundError("User")

4. Classify at the outermost boundary, propagate typed errors everywhere else

A single centralized error handler at the outermost layer maps error types to HTTP status codes. Business logic throws typed errors; the handler translates them. Service and repository layers must not know about HTTP. Every mainstream web framework ships a hook for this handler — Express middleware, FastAPI exception_handler, Flask errorhandler, Django middleware, Gin Use(ErrorHandler()), Axum HandleErrorLayer — the name varies, the shape doesn't.

Shared service layer (language-neutral in intent):

// service: throws typed errors, knows nothing about HTTP
async function getUser(id, actor) {
  const user = await repo.findById(id);
  if (!user) throw new NotFoundError('User');
  if (!canRead(actor, user)) throw new ForbiddenError('User');
  return user;
}

BAD — mapping scattered across controllers, duplicated on every route:

app.get('/users/:id', async (req, res) => {
  const user = await getUser(req.params.id);
  if (!user) return res.status(404).json({ error: 'not found' });
  if (!canRead(req.user, user)) return res.status(403).json({ error: 'forbidden' });
  res.json(user);
});

GOOD — one handler, every route benefits. Express 5:

const STATUS_MAP = {
  NotFoundError: 404,
  UnauthorizedError: 401,
  ForbiddenError: 403,
  ValidationError: 400,
  ConflictError: 409,
};
app.use((err, req, res, _next) => {
  const status = STATUS_MAP[err.constructor.name] ?? 500;
  if (status === 500) logger.error({ err }, 'unhandled error');
  res.status(status).json({ error: err.message });
});

GOOD — same pattern, FastAPI:

# boundary (runs once, for every route)
STATUS_MAP = {
    NotFoundError: 404,
    UnauthorizedError: 401,
    ForbiddenError: 403,
    ValidationError: 400,
    ConflictError: 409,
}

@app.exception_handler(AppError)
async def app_error_handler(request: Request, exc: AppError):
    status = STATUS_MAP.get(type(exc), 500)
    if status == 500:
        logger.error("unhandled error", exc_info=exc)
    return JSONResponse(status_code=status, content={"error": str(exc)})

One place per service owns the HTTP status mapping. Non-HTTP boundaries (gRPC, GraphQL, message queues) need their own classification layer — don't paste the HTTP shape into them and hope.

Anti-patterns

  • ❌ Empty catch blocks (catch {} / except: pass)
  • catch (e) { return null } — turns errors into silent wrong behavior
  • ❌ Checking error message strings (if (e.message.includes('not found'))
  • ❌ Re-throwing a different error type that loses the original stack trace — use cause: throw new AppError('msg', { cause: e })
  • ❌ Logging AND rethrowing at every layer — log once at the top boundary
  • ❌ HTTP status codes inside the service layer — services don't know about HTTP
  • ❌ Duplicating the status-code mapping across controllers — do it once in the error middleware

Keep looking

Skills are one crate of 328,083. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.