Claude Agent Skill · by Wshobson

Error Handling Patterns

The error-handling-patterns skill teaches developers how to implement robust error handling across multiple programming languages using techniques like exceptio

Install
Terminal · npx
$npx skills add https://github.com/wshobson/agents --skill error-handling-patterns
Works with Paperclip

How Error Handling Patterns fits into a Paperclip company.

Error Handling Patterns drops into any Paperclip agent that handles this kind of work. Assign it to a specialist inside a pre-configured PaperclipOrg company and the skill becomes available on every heartbeat — no prompt engineering, no tool wiring.

S
SaaS FactoryPaired

Pre-configured AI company — 18 agents, 18 skills, one-time purchase.

$27$59
Explore pack
Source file
SKILL.md632 lines
Expand
---name: error-handling-patternsdescription: Master error handling patterns across languages including exceptions, Result types, error propagation, and graceful degradation to build resilient applications. Use when implementing error handling, designing APIs, or improving application reliability.--- # Error Handling Patterns Build resilient applications with robust error handling strategies that gracefully handle failures and provide excellent debugging experiences. ## When to Use This Skill - Implementing error handling in new features- Designing error-resilient APIs- Debugging production issues- Improving application reliability- Creating better error messages for users and developers- Implementing retry and circuit breaker patterns- Handling async/concurrent errors- Building fault-tolerant distributed systems ## Core Concepts ### 1. Error Handling Philosophies **Exceptions vs Result Types:** - **Exceptions**: Traditional try-catch, disrupts control flow- **Result Types**: Explicit success/failure, functional approach- **Error Codes**: C-style, requires discipline- **Option/Maybe Types**: For nullable values **When to Use Each:** - Exceptions: Unexpected errors, exceptional conditions- Result Types: Expected errors, validation failures- Panics/Crashes: Unrecoverable errors, programming bugs ### 2. Error Categories **Recoverable Errors:** - Network timeouts- Missing files- Invalid user input- API rate limits **Unrecoverable Errors:** - Out of memory- Stack overflow- Programming bugs (null pointer, etc.) ## Language-Specific Patterns ### Python Error Handling **Custom Exception Hierarchy:** ```pythonclass ApplicationError(Exception):    """Base exception for all application errors."""    def __init__(self, message: str, code: str = None, details: dict = None):        super().__init__(message)        self.code = code        self.details = details or {}        self.timestamp = datetime.utcnow() class ValidationError(ApplicationError):    """Raised when validation fails."""    pass class NotFoundError(ApplicationError):    """Raised when resource not found."""    pass class ExternalServiceError(ApplicationError):    """Raised when external service fails."""    def __init__(self, message: str, service: str, **kwargs):        super().__init__(message, **kwargs)        self.service = service # Usagedef get_user(user_id: str) -> User:    user = db.query(User).filter_by(id=user_id).first()    if not user:        raise NotFoundError(            f"User not found",            code="USER_NOT_FOUND",            details={"user_id": user_id}        )    return user``` **Context Managers for Cleanup:** ```pythonfrom contextlib import contextmanager @contextmanagerdef database_transaction(session):    """Ensure transaction is committed or rolled back."""    try:        yield session        session.commit()    except Exception as e:        session.rollback()        raise    finally:        session.close() # Usagewith database_transaction(db.session) as session:    user = User(name="Alice")    session.add(user)    # Automatic commit or rollback``` **Retry with Exponential Backoff:** ```pythonimport timefrom functools import wrapsfrom typing import TypeVar, Callable T = TypeVar('T') def retry(    max_attempts: int = 3,    backoff_factor: float = 2.0,    exceptions: tuple = (Exception,)):    """Retry decorator with exponential backoff."""    def decorator(func: Callable[..., T]) -> Callable[..., T]:        @wraps(func)        def wrapper(*args, **kwargs) -> T:            last_exception = None            for attempt in range(max_attempts):                try:                    return func(*args, **kwargs)                except exceptions as e:                    last_exception = e                    if attempt < max_attempts - 1:                        sleep_time = backoff_factor ** attempt                        time.sleep(sleep_time)                        continue                    raise            raise last_exception        return wrapper    return decorator # Usage@retry(max_attempts=3, exceptions=(NetworkError,))def fetch_data(url: str) -> dict:    response = requests.get(url, timeout=5)    response.raise_for_status()    return response.json()``` ### TypeScript/JavaScript Error Handling **Custom Error Classes:** ```typescript// Custom error classesclass ApplicationError extends Error {  constructor(    message: string,    public code: string,    public statusCode: number = 500,    public details?: Record<string, any>,  ) {    super(message);    this.name = this.constructor.name;    Error.captureStackTrace(this, this.constructor);  }} class ValidationError extends ApplicationError {  constructor(message: string, details?: Record<string, any>) {    super(message, "VALIDATION_ERROR", 400, details);  }} class NotFoundError extends ApplicationError {  constructor(resource: string, id: string) {    super(`${resource} not found`, "NOT_FOUND", 404, { resource, id });  }} // Usagefunction getUser(id: string): User {  const user = users.find((u) => u.id === id);  if (!user) {    throw new NotFoundError("User", id);  }  return user;}``` **Result Type Pattern:** ```typescript// Result type for explicit error handlingtype Result<T, E = Error> = { ok: true; value: T } | { ok: false; error: E }; // Helper functionsfunction Ok<T>(value: T): Result<T, never> {  return { ok: true, value };} function Err<E>(error: E): Result<never, E> {  return { ok: false, error };} // Usagefunction parseJSON<T>(json: string): Result<T, SyntaxError> {  try {    const value = JSON.parse(json) as T;    return Ok(value);  } catch (error) {    return Err(error as SyntaxError);  }} // Consuming Resultconst result = parseJSON<User>(userJson);if (result.ok) {  console.log(result.value.name);} else {  console.error("Parse failed:", result.error.message);} // Chaining Resultsfunction chain<T, U, E>(  result: Result<T, E>,  fn: (value: T) => Result<U, E>,): Result<U, E> {  return result.ok ? fn(result.value) : result;}``` **Async Error Handling:** ```typescript// Async/await with proper error handlingasync function fetchUserOrders(userId: string): Promise<Order[]> {  try {    const user = await getUser(userId);    const orders = await getOrders(user.id);    return orders;  } catch (error) {    if (error instanceof NotFoundError) {      return []; // Return empty array for not found    }    if (error instanceof NetworkError) {      // Retry logic      return retryFetchOrders(userId);    }    // Re-throw unexpected errors    throw error;  }} // Promise error handlingfunction fetchData(url: string): Promise<Data> {  return fetch(url)    .then((response) => {      if (!response.ok) {        throw new NetworkError(`HTTP ${response.status}`);      }      return response.json();    })    .catch((error) => {      console.error("Fetch failed:", error);      throw error;    });}``` ### Rust Error Handling **Result and Option Types:** ```rustuse std::fs::File;use std::io::{self, Read}; // Result type for operations that can failfn read_file(path: &str) -> Result<String, io::Error> {    let mut file = File::open(path)?;  // ? operator propagates errors    let mut contents = String::new();    file.read_to_string(&mut contents)?;    Ok(contents)} // Custom error types#[derive(Debug)]enum AppError {    Io(io::Error),    Parse(std::num::ParseIntError),    NotFound(String),    Validation(String),} impl From<io::Error> for AppError {    fn from(error: io::Error) -> Self {        AppError::Io(error)    }} // Using custom error typefn read_number_from_file(path: &str) -> Result<i32, AppError> {    let contents = read_file(path)?;  // Auto-converts io::Error    let number = contents.trim().parse()        .map_err(AppError::Parse)?;   // Explicitly convert ParseIntError    Ok(number)} // Option for nullable valuesfn find_user(id: &str) -> Option<User> {    users.iter().find(|u| u.id == id).cloned()} // Combining Option and Resultfn get_user_age(id: &str) -> Result<u32, AppError> {    find_user(id)        .ok_or_else(|| AppError::NotFound(id.to_string()))        .map(|user| user.age)}``` ### Go Error Handling **Explicit Error Returns:** ```go// Basic error handlingfunc getUser(id string) (*User, error) {    user, err := db.QueryUser(id)    if err != nil {        return nil, fmt.Errorf("failed to query user: %w", err)    }    if user == nil {        return nil, errors.New("user not found")    }    return user, nil} // Custom error typestype ValidationError struct {    Field   string    Message string} func (e *ValidationError) Error() string {    return fmt.Sprintf("validation failed for %s: %s", e.Field, e.Message)} // Sentinel errors for comparisonvar (    ErrNotFound     = errors.New("not found")    ErrUnauthorized = errors.New("unauthorized")    ErrInvalidInput = errors.New("invalid input")) // Error checkinguser, err := getUser("123")if err != nil {    if errors.Is(err, ErrNotFound) {        // Handle not found    } else {        // Handle other errors    }} // Error wrapping and unwrappingfunc processUser(id string) error {    user, err := getUser(id)    if err != nil {        return fmt.Errorf("process user failed: %w", err)    }    // Process user    return nil} // Unwrap errorserr := processUser("123")if err != nil {    var valErr *ValidationError    if errors.As(err, &valErr) {        fmt.Printf("Validation error: %s\n", valErr.Field)    }}``` ## Universal Patterns ### Pattern 1: Circuit Breaker Prevent cascading failures in distributed systems. ```pythonfrom enum import Enumfrom datetime import datetime, timedeltafrom typing import Callable, TypeVar T = TypeVar('T') class CircuitState(Enum):    CLOSED = "closed"       # Normal operation    OPEN = "open"          # Failing, reject requests    HALF_OPEN = "half_open"  # Testing if recovered class CircuitBreaker:    def __init__(        self,        failure_threshold: int = 5,        timeout: timedelta = timedelta(seconds=60),        success_threshold: int = 2    ):        self.failure_threshold = failure_threshold        self.timeout = timeout        self.success_threshold = success_threshold        self.failure_count = 0        self.success_count = 0        self.state = CircuitState.CLOSED        self.last_failure_time = None     def call(self, func: Callable[[], T]) -> T:        if self.state == CircuitState.OPEN:            if datetime.now() - self.last_failure_time > self.timeout:                self.state = CircuitState.HALF_OPEN                self.success_count = 0            else:                raise Exception("Circuit breaker is OPEN")         try:            result = func()            self.on_success()            return result        except Exception as e:            self.on_failure()            raise     def on_success(self):        self.failure_count = 0        if self.state == CircuitState.HALF_OPEN:            self.success_count += 1            if self.success_count >= self.success_threshold:                self.state = CircuitState.CLOSED                self.success_count = 0     def on_failure(self):        self.failure_count += 1        self.last_failure_time = datetime.now()        if self.failure_count >= self.failure_threshold:            self.state = CircuitState.OPEN # Usagecircuit_breaker = CircuitBreaker() def fetch_data():    return circuit_breaker.call(lambda: external_api.get_data())``` ### Pattern 2: Error Aggregation Collect multiple errors instead of failing on first error. ```typescriptclass ErrorCollector {  private errors: Error[] = [];   add(error: Error): void {    this.errors.push(error);  }   hasErrors(): boolean {    return this.errors.length > 0;  }   getErrors(): Error[] {    return [...this.errors];  }   throw(): never {    if (this.errors.length === 1) {      throw this.errors[0];    }    throw new AggregateError(      this.errors,      `${this.errors.length} errors occurred`,    );  }} // Usage: Validate multiple fieldsfunction validateUser(data: any): User {  const errors = new ErrorCollector();   if (!data.email) {    errors.add(new ValidationError("Email is required"));  } else if (!isValidEmail(data.email)) {    errors.add(new ValidationError("Email is invalid"));  }   if (!data.name || data.name.length < 2) {    errors.add(new ValidationError("Name must be at least 2 characters"));  }   if (!data.age || data.age < 18) {    errors.add(new ValidationError("Age must be 18 or older"));  }   if (errors.hasErrors()) {    errors.throw();  }   return data as User;}``` ### Pattern 3: Graceful Degradation Provide fallback functionality when errors occur. ```pythonfrom typing import Optional, Callable, TypeVar T = TypeVar('T') def with_fallback(    primary: Callable[[], T],    fallback: Callable[[], T],    log_error: bool = True) -> T:    """Try primary function, fall back to fallback on error."""    try:        return primary()    except Exception as e:        if log_error:            logger.error(f"Primary function failed: {e}")        return fallback() # Usagedef get_user_profile(user_id: str) -> UserProfile:    return with_fallback(        primary=lambda: fetch_from_cache(user_id),        fallback=lambda: fetch_from_database(user_id)    ) # Multiple fallbacksdef get_exchange_rate(currency: str) -> float:    return (        try_function(lambda: api_provider_1.get_rate(currency))        or try_function(lambda: api_provider_2.get_rate(currency))        or try_function(lambda: cache.get_rate(currency))        or DEFAULT_RATE    ) def try_function(func: Callable[[], Optional[T]]) -> Optional[T]:    try:        return func()    except Exception:        return None``` ## Best Practices 1. **Fail Fast**: Validate input early, fail quickly2. **Preserve Context**: Include stack traces, metadata, timestamps3. **Meaningful Messages**: Explain what happened and how to fix it4. **Log Appropriately**: Error = log, expected failure = don't spam logs5. **Handle at Right Level**: Catch where you can meaningfully handle6. **Clean Up Resources**: Use try-finally, context managers, defer7. **Don't Swallow Errors**: Log or re-throw, don't silently ignore8. **Type-Safe Errors**: Use typed errors when possible ```python# Good error handling exampledef process_order(order_id: str) -> Order:    """Process order with comprehensive error handling."""    try:        # Validate input        if not order_id:            raise ValidationError("Order ID is required")         # Fetch order        order = db.get_order(order_id)        if not order:            raise NotFoundError("Order", order_id)         # Process payment        try:            payment_result = payment_service.charge(order.total)        except PaymentServiceError as e:            # Log and wrap external service error            logger.error(f"Payment failed for order {order_id}: {e}")            raise ExternalServiceError(                f"Payment processing failed",                service="payment_service",                details={"order_id": order_id, "amount": order.total}            ) from e         # Update order        order.status = "completed"        order.payment_id = payment_result.id        db.save(order)         return order     except ApplicationError:        # Re-raise known application errors        raise    except Exception as e:        # Log unexpected errors        logger.exception(f"Unexpected error processing order {order_id}")        raise ApplicationError(            "Order processing failed",            code="INTERNAL_ERROR"        ) from e``` ## Common Pitfalls - **Catching Too Broadly**: `except Exception` hides bugs- **Empty Catch Blocks**: Silently swallowing errors- **Logging and Re-throwing**: Creates duplicate log entries- **Not Cleaning Up**: Forgetting to close files, connections- **Poor Error Messages**: "Error occurred" is not helpful- **Returning Error Codes**: Use exceptions or Result types- **Ignoring Async Errors**: Unhandled promise rejections