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;
}
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
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
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 }; }
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);
}
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);
}
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 }
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]
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)
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
/code-review checks for swallowed errors, missing error handlers, and inconsistent patterns.
Code Review Pack (¥980) on PromptWorks.
Myouga (@myougatheaxo) — Security-focused Claude Code engineer.
Top comments (0)