Claude-skill-registry exception-taxonomy
Hierarchical exception system with HTTP status codes, machine-readable error codes, and structured responses for consistent API error handling across all endpoints.
install
source · Clone the upstream repo
git clone https://github.com/majiayu000/claude-skill-registry
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/exception-taxonomy" ~/.claude/skills/majiayu000-claude-skill-registry-exception-taxonomy && rm -rf "$T"
manifest:
skills/data/exception-taxonomy/SKILL.mdsource content
Exception Taxonomy
Hierarchical exception system with HTTP status codes, error codes, and structured responses for consistent API error handling.
When to Use This Skill
- Building APIs that need consistent error responses
- Creating machine-readable error codes for client handling
- Implementing retry logic based on error types
- Standardizing error handling across a large codebase
Core Concepts
A well-designed exception taxonomy provides:
- Consistent error responses across all endpoints
- Machine-readable error codes for client handling
- Human-readable messages for debugging
- HTTP status code mapping
- Retry hints for transient failures
The hierarchy typically follows:
BaseAppError (abstract) ├── AuthenticationError (401) ├── AuthorizationError (403) ├── ResourceError (404/409) ├── ValidationError (422) ├── RateLimitError (429) ├── ExternalServiceError (502/503) └── PaymentError (402)
Implementation
Python
from dataclasses import dataclass, field from typing import Optional, Dict, Any from enum import Enum class ErrorCode(str, Enum): """Standardized error codes for API responses.""" # Authentication AUTH_INVALID_CREDENTIALS = "AUTH_INVALID_CREDENTIALS" AUTH_TOKEN_EXPIRED = "AUTH_TOKEN_EXPIRED" AUTH_TOKEN_INVALID = "AUTH_TOKEN_INVALID" AUTH_EMAIL_EXISTS = "AUTH_EMAIL_EXISTS" # Authorization FORBIDDEN = "FORBIDDEN" # Resources RESOURCE_NOT_FOUND = "RESOURCE_NOT_FOUND" RESOURCE_CONFLICT = "RESOURCE_CONFLICT" # Rate Limiting RATE_LIMIT_EXCEEDED = "RATE_LIMIT_EXCEEDED" # External Services GENERATION_FAILED = "GENERATION_FAILED" GENERATION_TIMEOUT = "GENERATION_TIMEOUT" # Validation VALIDATION_ERROR = "VALIDATION_ERROR" INVALID_STATE_TRANSITION = "INVALID_STATE_TRANSITION" @dataclass class BaseAppError(Exception): """Base exception for all application errors.""" message: str code: ErrorCode status_code: int = 500 details: Optional[Dict[str, Any]] = field(default_factory=dict) retry_after: Optional[int] = None def __post_init__(self): super().__init__(self.message) def to_dict(self) -> Dict[str, Any]: """Convert to API response format.""" error_dict = { "error": { "message": self.message, "code": self.code.value, } } if self.details: error_dict["error"]["details"] = self.details if self.retry_after is not None: error_dict["error"]["retry_after"] = self.retry_after return error_dict @dataclass class NotFoundError(BaseAppError): """Resource not found error.""" resource_type: str = "resource" resource_id: str = "" message: str = field(init=False) code: ErrorCode = field(default=ErrorCode.RESOURCE_NOT_FOUND) status_code: int = 404 def __post_init__(self): self.message = f"{self.resource_type.title()} not found" self.details = { "resource_type": self.resource_type, "resource_id": self.resource_id, } super().__post_init__() @dataclass class RateLimitError(BaseAppError): """Rate limit exceeded error.""" retry_after: int = 60 message: str = "Rate limit exceeded" code: ErrorCode = field(default=ErrorCode.RATE_LIMIT_EXCEEDED) status_code: int = 429 def __post_init__(self): self.details = {"retry_after": self.retry_after} super().__post_init__() @dataclass class InvalidStateTransitionError(BaseAppError): """Invalid state transition error.""" current_status: str = "" target_status: str = "" message: str = field(init=False) code: ErrorCode = field(default=ErrorCode.INVALID_STATE_TRANSITION) status_code: int = 409 def __post_init__(self): self.message = f"Cannot transition from '{self.current_status}' to '{self.target_status}'" self.details = { "current_status": self.current_status, "target_status": self.target_status, } super().__post_init__()
TypeScript
export enum ErrorCode { AUTH_INVALID_CREDENTIALS = 'AUTH_INVALID_CREDENTIALS', AUTH_TOKEN_EXPIRED = 'AUTH_TOKEN_EXPIRED', AUTH_TOKEN_INVALID = 'AUTH_TOKEN_INVALID', FORBIDDEN = 'FORBIDDEN', RESOURCE_NOT_FOUND = 'RESOURCE_NOT_FOUND', RESOURCE_CONFLICT = 'RESOURCE_CONFLICT', RATE_LIMIT_EXCEEDED = 'RATE_LIMIT_EXCEEDED', VALIDATION_ERROR = 'VALIDATION_ERROR', INVALID_STATE_TRANSITION = 'INVALID_STATE_TRANSITION', } interface ErrorDetails { [key: string]: unknown; } export class BaseAppError extends Error { constructor( public readonly message: string, public readonly code: ErrorCode, public readonly statusCode: number = 500, public readonly details: ErrorDetails = {}, public readonly retryAfter?: number ) { super(message); this.name = this.constructor.name; } toJSON() { const error: Record<string, unknown> = { message: this.message, code: this.code, }; if (Object.keys(this.details).length > 0) { error.details = this.details; } if (this.retryAfter !== undefined) { error.retry_after = this.retryAfter; } return { error }; } } export class NotFoundError extends BaseAppError { constructor(resourceType: string, resourceId: string) { super( `${resourceType.charAt(0).toUpperCase() + resourceType.slice(1)} not found`, ErrorCode.RESOURCE_NOT_FOUND, 404, { resource_type: resourceType, resource_id: resourceId } ); } } export class RateLimitError extends BaseAppError { constructor(retryAfter: number = 60) { super( 'Rate limit exceeded', ErrorCode.RATE_LIMIT_EXCEEDED, 429, { retry_after: retryAfter }, retryAfter ); } } export class InvalidStateTransitionError extends BaseAppError { constructor(currentStatus: string, targetStatus: string) { super( `Cannot transition from '${currentStatus}' to '${targetStatus}'`, ErrorCode.INVALID_STATE_TRANSITION, 409, { current_status: currentStatus, target_status: targetStatus } ); } }
Usage Examples
FastAPI Exception Handlers
from fastapi import FastAPI, Request from fastapi.responses import JSONResponse app = FastAPI() @app.exception_handler(BaseAppError) async def app_error_handler(request: Request, exc: BaseAppError) -> JSONResponse: headers = {"Retry-After": str(exc.retry_after)} if exc.retry_after else None return JSONResponse( status_code=exc.status_code, content=exc.to_dict(), headers=headers, ) @app.exception_handler(Exception) async def generic_error_handler(request: Request, exc: Exception) -> JSONResponse: logger.exception(f"Unexpected error: {exc}") return JSONResponse( status_code=500, content={"error": {"message": "An unexpected error occurred", "code": "INTERNAL_ERROR"}}, )
Route Usage
@router.get("/jobs/{job_id}") async def get_job(job_id: str, user_id: str = Depends(get_current_user)): job = await job_service.get(job_id) if not job: raise NotFoundError(resource_type="job", resource_id=job_id) if job.user_id != user_id: raise AuthorizationError(resource_type="job") return job
Client-Side Handling (TypeScript)
interface APIError { error: { message: string; code: string; details?: Record<string, unknown>; retry_after?: number; }; } function handleAPIError(error: APIError): void { switch (error.error.code) { case 'AUTH_TOKEN_EXPIRED': authStore.refreshToken(); break; case 'RATE_LIMIT_EXCEEDED': const retryAfter = error.error.retry_after || 60; toast.error(`Rate limited. Try again in ${retryAfter}s`); break; default: toast.error(error.error.message); } }
Best Practices
- Use specific exceptions - Create domain-specific exceptions rather than generic ones
- Include context - Always include relevant IDs and state in error details
- Map to HTTP codes - Each exception should have a clear HTTP status code
- Provide retry hints - For transient failures, include
retry_after - Use error codes - Machine-readable codes enable client-side handling logic
- Log appropriately - Log full details server-side, return safe messages to clients
Common Mistakes
- Using generic exceptions instead of domain-specific ones
- Forgetting to include resource IDs in error details
- Not providing retry hints for rate limit errors
- Exposing internal error details in production responses
- Inconsistent error response formats across endpoints
Related Patterns
- error-sanitization - Sanitize errors before returning to users
- error-handling - General error handling patterns
- rate-limiting - Rate limiting implementation