DEV Community

myougaTheAxo
myougaTheAxo

Posted on

Error Handling Patterns with Claude Code: Consistent, Testable, User-Friendly

Error handling is one of those areas where code quality varies wildly across a codebase. Claude Code can establish consistent patterns — if you tell it what pattern to use.


The Problem with Default Error Handling

Without explicit instructions, Claude Code might generate:

// Inconsistent — different error handling in every function
async function getUser(id: string) {
  try {
    const user = await db.user.findUnique({ where: { id } });
    return user;
  } catch (error) {
    console.error(error); // logs and swallows
    return null;
  }
}

async function createPost(data: PostInput) {
  const post = await db.post.create({ data }); // no error handling
  return post;
}
Enter fullscreen mode Exit fullscreen mode

Tell Claude Code Your Error Pattern

First, establish your error handling pattern in CLAUDE.md:

## Error Handling Pattern

### Error Classes
- Use custom error classes (extend AppError from src/errors/AppError.ts)
- ValidationError for invalid input
- NotFoundError for missing resources
- AuthorizationError for permission issues
- ExternalServiceError for third-party API failures

### Rules
- Never swallow errors silently (no `catch(e) {}`)
- Never return null to indicate error — throw or use Result type
- Log errors with context (not just the error itself)
- User-facing messages must not include internal details

### Pattern (Result type for services)
- Services return Result<T, AppError>
- Controllers/Routes unwrap and send HTTP responses
- Unhandled errors bubble up to the global error handler

### Global Error Handler
- Location: src/middleware/errorHandler.ts
- All unhandled errors go here
- Maps AppError subclasses to HTTP status codes
Enter fullscreen mode Exit fullscreen mode

Custom Error Classes

Ask Claude Code to generate an error hierarchy:

Generate the AppError class hierarchy for this project.
Requirements:
- Base AppError with code, message, httpStatus
- ValidationError (400)
- NotFoundError (404)
- AuthorizationError (403)
- ExternalServiceError (502)
- All errors must be JSON-serializable
- Include TypeScript types

Location: src/errors/AppError.ts
Enter fullscreen mode Exit fullscreen mode

Result Type Pattern (No Throwing in Services)

## Result Type
Services should return Result<T, AppError> instead of throwing:

type Result<T, E = AppError> =
  | { ok: true; value: T }
  | { ok: false; error: E };

function ok<T>(value: T): Result<T> { return { ok: true, value }; }
function err<E>(error: E): Result<never, E> { return { ok: false, error }; }
Enter fullscreen mode Exit fullscreen mode

Generated service code:

async function getUser(id: string): Promise<Result<User, AppError>> {
  const user = await db.user.findUnique({ where: { id } });
  if (!user) return err(new NotFoundError(`User ${id} not found`));
  return ok(user);
}
Enter fullscreen mode Exit fullscreen mode

Generated controller code:

async function getUserHandler(req: Request, res: Response) {
  const result = await getUser(req.params.id);
  if (!result.ok) {
    if (result.error instanceof NotFoundError) {
      return res.status(404).json({ error: result.error.message });
    }
    return res.status(500).json({ error: 'Internal server error' });
  }
  return res.json(result.value);
}
Enter fullscreen mode Exit fullscreen mode

Global Error Handler

Generate a global error handler middleware for Express.
Requirements:
- Handle AppError subclasses with their httpStatus
- Handle Prisma errors (P2002 = conflict, P2025 = not found)
- Handle validation errors from Zod
- Never expose stack traces in production
- Log all 5xx errors with context
- Return consistent JSON format: { error: string, code?: string }
Enter fullscreen mode Exit fullscreen mode

Error Handling in Tests

Write tests that verify the error handling in this service function.
Test cases needed:
- Successful case
- NotFoundError case (verify HTTP 404)
- ValidationError case (verify HTTP 400)
- Database error case (verify HTTP 500 with generic message)

[paste the function]
Enter fullscreen mode Exit fullscreen mode

Hook: Detect Swallowed Errors

# .claude/hooks/check_errors.py
import json, re, sys

data = json.load(sys.stdin)
content = data.get("tool_input", {}).get("content", "") or ""
fp = data.get("tool_input", {}).get("file_path", "")

if not content or not fp.endswith((".ts", ".js")):
    sys.exit(0)

bad_patterns = [
    (r'catch\s*\([^)]*\)\s*\{\s*\}', "Empty catch block (swallowed error)"),
    (r'catch\s*\([^)]*\)\s*\{\s*return null', "Returning null on error"),
    (r'catch\s*\([^)]*\)\s*\{\s*return undefined', "Returning undefined on error"),
    (r'catch\s*\([^)]*\)\s*\{\s*console\.error', "console.error without re-throw"),
]

for pattern, msg in bad_patterns:
    if re.search(pattern, content, re.DOTALL):
        print(f"[ERROR] {msg}", file=sys.stderr)

sys.exit(0)
Enter fullscreen mode Exit fullscreen mode

CLAUDE.md Global Error Handler Reference

## Error Handling References
- Base class: src/errors/AppError.ts
- Global handler: src/middleware/errorHandler.ts
- Logging: src/lib/logger.ts (with request context)
- Zod error parser: src/lib/zodErrors.ts

Pattern: throw errors in services → catch in global handler → return consistent JSON
Enter fullscreen mode Exit fullscreen mode

/code-review checks for swallowed errors, missing error handlers, and inconsistent patterns.

Code Review Pack (¥980) on PromptWorks.

👉 prompt-works.jp

Myouga (@myougatheaxo) — Security-focused Claude Code engineer.

Top comments (0)