error-handling

Implement robust error handling with exceptions, retry logic, circuit breakers, and graceful degradation. Use when designing error handling strategies, implementing retry policies, adding circuit brea

SKILL.md

GitHub

Error Handling

Purpose: Handle failures gracefully with logging, retries, and circuit breakers. Goal: No silent failures, clear error messages, system resilience. Note: For language-specific implementations, see C# Development or Python Development.


When to Use This Skill

  • Designing error handling strategies
  • Implementing retry policies with backoff
  • Adding circuit breakers for external services
  • Configuring request timeouts
  • Building health check endpoints

Prerequisites

  • Understanding of exception hierarchies in target language
  • Resilience library available

Decision Tree

Handling an error?
+- Expected failure (validation, not-found)?
| - Return error result/status code, don't throw
+- Unexpected failure (network, I/O, timeout)?
| +- Transient? -> Retry with exponential backoff
| | - Still failing after retries? -> Circuit breaker
| - Permanent? -> Log + return error response
+- What to catch?
| +- Specific exception -> catch specific, handle specifically
| +- Base exception -> only at top-level boundaries
| - NEVER catch and swallow silently
+- Error response format?
| +- API? -> RFC 7807 Problem Details
| - UI? -> User-friendly message + log technical details
- Logging?
 - Always include: correlation ID, exception type, stack trace, context

Exception Handling

Custom Exception Types

Define Specific Exceptions:

Exception Hierarchy:
 AppException (base)
 +- ValidationException
 +- NotFoundException 
 +- UnauthorizedException
 +- ForbiddenException
 - ExternalServiceException

Benefits:

  • Catch specific errors
  • Provide context in exception
  • Different handling per type
  • Clear error messages

Try-Catch-Finally Pattern

function processPayment(amount, paymentMethod):
 try:
 # Attempt operation
 validatePaymentMethod(paymentMethod)
 chargeResult = paymentGateway.charge(amount, paymentMethod)
 
 # Log success
 logger.info("Payment processed", {amount, paymentMethod})
 
 return chargeResult
 
 catch ValidationException as error:
 # Handle validation errors
 logger.warn("Invalid payment method", {error, paymentMethod})
 throw error
 
 catch NetworkException as error:
 # Handle network errors with retry
 logger.error("Payment gateway unavailable", {error})
 throw ExternalServiceException("Payment service temporarily unavailable")
 
 finally:
 # Always execute (cleanup resources)
 releasePaymentLock(paymentMethod)

Global Error Handler

Centralized Error Handling:

# HTTP API Error Handler
function handleHttpError(error, request, response):
 # Log error with context
 logger.error("Request failed", {
 error: error.message,
 stack: error.stack,
 requestId: request.id,
 path: request.path,
 method: request.method
 })
 
 # Map exception to HTTP status
 statusCode = mapExceptionToStatusCode(error)
 
 # Return user-friendly response
 return response.status(statusCode).json({
 error: error.userMessage,
 requestId: request.id,
 timestamp: currentTime()
 })

function mapExceptionToStatusCode(error):
 if error is NotFoundException: return 404
 if error is ValidationException: return 400
 if error is UnauthorizedException: return 401
 if error is ForbiddenException: return 403
 if error is ExternalServiceException: return 503
 return 500 # Internal Server Error

Common Error Handling Patterns

Pattern Use Case Example
Retry Transient failures Network timeouts, rate limits
Circuit Breaker Prevent cascading failures External service calls
Fallback Provide alternative Default values, cached data
Timeout Prevent hanging Long-running operations
Bulkhead Isolate resources Separate thread pools per service
Rate Limiting Protect from overload API throttling

Error Handling Anti-Patterns

[FAIL] Swallow Exceptions:

try:
 riskyOperation()
catch:
 # Do nothing - ERROR! No one knows it failed

[FAIL] Generic Catch-All:

try:
 operation()
catch Exception:
 return null # Hides what went wrong

[FAIL] Exception for Flow Control:

try:
 user = database.findUser(id)
catch NotFoundException:
 # Using exceptions for normal flow - BAD
 user = createNewUser(id)

[PASS] Proper Error Handling:

try:
 user = database.findUser(id)
catch NotFoundException as error:
 logger.warn("User not found", {id})
 throw error # Re-throw, don't hide
catch DatabaseException as error:
 logger.error("Database error", {error, id})
 throw ServiceUnavailableException("User service temporarily unavailable")

Resilience Patterns Summary

Resilience Stack (Apply Multiple Patterns):

 -------------------------------------
 | Rate Limiting (Protect your service)|
 -------------------------------------
 (down)
 -------------------------------------
 | Timeout (Prevent hanging) |
 -------------------------------------
 (down)
 -------------------------------------
 | Circuit Breaker (Fail fast) |
 -------------------------------------
 (down)
 -------------------------------------
 | Retry (Handle transient failures) |
 -------------------------------------
 (down)
 -------------------------------------
 | Fallback (Provide alternative) |
 -------------------------------------

Resources

Resilience Libraries:

  • .NET: Polly, Microsoft.Extensions.Resilience
  • Python: tenacity, resilience4py
  • Node.js: opossum (circuit breaker), async-retry
  • Java: Resilience4j, Hystrix (deprecated)
  • Go: go-resilience, go-retry

Patterns:


See Also: Skills.md - AGENTS.md

Last Updated: January 27, 2026

Troubleshooting

Issue Solution
Retry storm overwhelming service Add exponential backoff with jitter, set max retry count
Circuit breaker stuck open Check half-open state configuration, verify health endpoint responds
Swallowed exceptions hiding bugs Always log exceptions before fallback, use structured logging with stack traces

References