スキル一覧に戻る
srstomp

error-handling

by srstomp

An orchestration plugin for Claude Code that enables reliable autonomous development sessions with configurable checkpoints and task management.

0🍴 0📅 2026年1月23日
GitHubで見るManusで実行

SKILL.md


name: error-handling description: Comprehensive error handling patterns for robust applications including custom errors, error boundaries, recovery strategies, and user-facing error messages. Use this skill when designing error hierarchies, implementing React error boundaries, adding retry logic or fallbacks, creating API error responses, integrating error tracking (Sentry), or improving user error communication. Triggers on "error handling", "error boundary", "custom error", "retry logic", "graceful degradation", "error tracking", "Sentry", "user error message", "try-catch", "Result type", "circuit breaker".

Error Handling

Design resilient applications through intentional error handling strategies.

Error Handling Philosophy

┌─────────────────────────────────────────────────────────────────┐
│                    Error Handling Goals                         │
├─────────────────┬─────────────────┬─────────────────────────────┤
│   Debuggable    │   Recoverable   │      User-Friendly          │
│ "What failed?"  │ "Can we retry?" │    "What should I do?"      │
├─────────────────┼─────────────────┼─────────────────────────────┤
│ Rich context    │ Retry logic     │ Clear, actionable messages  │
│ Stack traces    │ Fallbacks       │ Appropriate detail level    │
│ Correlation IDs │ Circuit breaker │ Recovery guidance           │
└─────────────────┴─────────────────┴─────────────────────────────┘

Core principle: Errors are data, not just exceptions. Design them intentionally.

Quick Decision Guide

SituationPatternReference
Need typed error categoriesCustom error classeserror-patterns.md
Want explicit error handling (no throws)Result/Either typeerror-patterns.md
React component might crashError boundaryreact-errors.md
API endpoint error responseStructured API errorsapi-errors.md
Network calls that might failRetry with backoffrecovery-patterns.md
Downstream service unreliableCircuit breakerrecovery-patterns.md
Feature can work without dependencyGraceful degradationrecovery-patterns.md

Error Type Design

Custom Error Classes

// Base error with context
class AppError extends Error {
  constructor(
    message: string,
    public readonly code: string,
    public readonly context?: Record<string, unknown>,
    public readonly cause?: Error
  ) {
    super(message);
    this.name = this.constructor.name;
    Error.captureStackTrace(this, this.constructor);
  }

  toJSON() {
    return {
      name: this.name,
      code: this.code,
      message: this.message,
      context: this.context,
    };
  }
}

// Domain-specific errors
class ValidationError extends AppError {
  constructor(message: string, public readonly field?: string) {
    super(message, 'VALIDATION_ERROR', { field });
  }
}

class NotFoundError extends AppError {
  constructor(resource: string, id: string) {
    super(`${resource} not found: ${id}`, 'NOT_FOUND', { resource, id });
  }
}

class AuthenticationError extends AppError {
  constructor(message = 'Authentication required') {
    super(message, 'AUTH_REQUIRED');
  }
}

class AuthorizationError extends AppError {
  constructor(action: string, resource: string) {
    super(`Not authorized to ${action} ${resource}`, 'FORBIDDEN', { action, resource });
  }
}

Error Code Registry

// Centralized error codes prevent collisions
const ErrorCodes = {
  // Auth: 1xxx
  AUTH_REQUIRED: 'E1001',
  AUTH_EXPIRED: 'E1002',
  AUTH_INVALID: 'E1003',
  
  // Validation: 2xxx
  VALIDATION_FAILED: 'E2001',
  VALIDATION_MISSING_FIELD: 'E2002',
  VALIDATION_INVALID_FORMAT: 'E2003',
  
  // Business Logic: 3xxx
  INSUFFICIENT_FUNDS: 'E3001',
  ITEM_OUT_OF_STOCK: 'E3002',
  
  // External Services: 4xxx
  SERVICE_UNAVAILABLE: 'E4001',
  RATE_LIMITED: 'E4002',
} as const;

Result Pattern (No Throws)

For explicit error handling without exceptions:

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

// Helper functions
const Ok = <T>(value: T): Result<T, never> => ({ ok: true, value });
const Err = <E>(error: E): Result<never, E> => ({ ok: false, error });

// Usage
async function parseUser(data: unknown): Result<User, ValidationError> {
  const parsed = userSchema.safeParse(data);
  if (!parsed.success) {
    return Err(new ValidationError(parsed.error.message));
  }
  return Ok(parsed.data);
}

// Consuming results
const result = await parseUser(input);
if (!result.ok) {
  logger.warn({ error: result.error, input });
  return res.status(400).json({ error: result.error.message });
}
const user = result.value; // Type-safe!

When to use Result vs throws:

  • Result: Expected failures (validation, business rules), functional pipelines
  • Throws: Unexpected failures (programmer errors, system failures)

React Error Boundaries

Basic Error Boundary

class ErrorBoundary extends Component<Props, State> {
  state = { hasError: false, error: null };

  static getDerivedStateFromError(error: Error) {
    return { hasError: true, error };
  }

  componentDidCatch(error: Error, info: ErrorInfo) {
    // Log to error tracking
    Sentry.captureException(error, { extra: info });
  }

  render() {
    if (this.state.hasError) {
      return this.props.fallback ?? <DefaultErrorUI error={this.state.error} />;
    }
    return this.props.children;
  }
}

Boundary Placement Strategy

App
├── ErrorBoundary (app-level: "Something went wrong")
│   └── Layout
│       ├── ErrorBoundary (route-level: show error page)
│       │   └── DashboardPage
│       │       ├── ErrorBoundary (widget-level: show widget error)
│       │       │   └── SalesChart
│       │       └── ErrorBoundary
│       │           └── RecentOrders

Rules:

  • App-level: Catches catastrophic failures, shows generic error
  • Route-level: Lets navigation continue, shows error page for route
  • Component-level: Isolates failures, shows component-specific fallback

API Error Responses

Standard Error Response Shape

interface APIError {
  error: {
    code: string;           // Machine-readable code
    message: string;        // Human-readable message
    details?: unknown;      // Additional context
    requestId?: string;     // Correlation ID for debugging
  };
}

// Example response
{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Invalid email format",
    "details": {
      "field": "email",
      "value": "not-an-email"
    },
    "requestId": "req_abc123"
  }
}

HTTP Status Code Mapping

Error TypeStatusWhen to Use
ValidationError400Invalid input from client
AuthenticationError401Missing or invalid auth
AuthorizationError403Valid auth, insufficient permissions
NotFoundError404Resource doesn't exist
ConflictError409State conflict (duplicate, version mismatch)
RateLimitError429Too many requests
InternalError500Unexpected server error
ServiceUnavailableError503Downstream service failure

Express Error Handler

function errorHandler(err: Error, req: Request, res: Response, next: NextFunction) {
  const requestId = req.headers['x-request-id'] as string;

  if (err instanceof AppError) {
    // Known error — safe to expose
    return res.status(getStatusCode(err)).json({
      error: {
        code: err.code,
        message: err.message,
        details: err.context,
        requestId,
      },
    });
  }

  // Unknown error — don't leak details
  logger.error({ err, requestId }, 'Unhandled error');
  Sentry.captureException(err);
  
  res.status(500).json({
    error: {
      code: 'INTERNAL_ERROR',
      message: 'An unexpected error occurred',
      requestId,
    },
  });
}

Recovery Strategies

Retry with Exponential Backoff

async function withRetry<T>(
  fn: () => Promise<T>,
  options: {
    maxAttempts?: number;
    baseDelay?: number;
    maxDelay?: number;
    shouldRetry?: (error: Error) => boolean;
  } = {}
): Promise<T> {
  const {
    maxAttempts = 3,
    baseDelay = 1000,
    maxDelay = 30000,
    shouldRetry = (e) => e instanceof NetworkError || e instanceof TimeoutError,
  } = options;

  let lastError: Error;
  
  for (let attempt = 1; attempt <= maxAttempts; attempt++) {
    try {
      return await fn();
    } catch (error) {
      lastError = error as Error;
      
      if (attempt === maxAttempts || !shouldRetry(lastError)) {
        throw lastError;
      }
      
      const delay = Math.min(baseDelay * 2 ** (attempt - 1), maxDelay);
      const jitter = delay * 0.1 * Math.random();
      await sleep(delay + jitter);
    }
  }
  
  throw lastError!;
}

Circuit Breaker Pattern

class CircuitBreaker {
  private failures = 0;
  private lastFailure?: Date;
  private state: 'closed' | 'open' | 'half-open' = 'closed';

  constructor(
    private threshold: number = 5,
    private timeout: number = 60000
  ) {}

  async execute<T>(fn: () => Promise<T>): Promise<T> {
    if (this.state === 'open') {
      if (Date.now() - this.lastFailure!.getTime() > this.timeout) {
        this.state = 'half-open';
      } else {
        throw new CircuitOpenError('Circuit breaker is open');
      }
    }

    try {
      const result = await fn();
      this.onSuccess();
      return result;
    } catch (error) {
      this.onFailure();
      throw error;
    }
  }

  private onSuccess() {
    this.failures = 0;
    this.state = 'closed';
  }

  private onFailure() {
    this.failures++;
    this.lastFailure = new Date();
    if (this.failures >= this.threshold) {
      this.state = 'open';
    }
  }
}

User-Facing Error Messages

Message Writing Principles

❌ "Error: ETIMEDOUT"
✅ "We're having trouble connecting. Please check your internet and try again."

❌ "Null pointer exception at line 42"  
✅ "Something went wrong. We've been notified and are working on it."

❌ "Validation failed"
✅ "Please enter a valid email address"

Guidelines:

  1. Be specific about the problem (when safe to do so)
  2. Suggest what to do next (retry, contact support, try different input)
  3. Never expose technical details (stack traces, internal codes)
  4. Match the tone to severity (casual for minor, serious for major)

Error Message Templates

const userMessages = {
  network: {
    offline: "You appear to be offline. Please check your connection.",
    timeout: "This is taking longer than expected. Please try again.",
    serverError: "We're experiencing technical difficulties. Please try again in a few minutes.",
  },
  validation: {
    required: (field: string) => `${field} is required`,
    invalid: (field: string) => `Please enter a valid ${field}`,
    tooLong: (field: string, max: number) => `${field} must be ${max} characters or less`,
  },
  auth: {
    expired: "Your session has expired. Please sign in again.",
    unauthorized: "You don't have permission to perform this action.",
  },
};

Error Tracking Integration

Sentry Setup

import * as Sentry from '@sentry/node';

Sentry.init({
  dsn: process.env.SENTRY_DSN,
  environment: process.env.NODE_ENV,
  tracesSampleRate: 0.1,
  beforeSend(event) {
    // Scrub sensitive data
    if (event.request?.data) {
      delete event.request.data.password;
    }
    return event;
  },
});

// Adding context
Sentry.setUser({ id: user.id, email: user.email });
Sentry.setTag('feature', 'checkout');
Sentry.setContext('order', { orderId, amount });

What to Track

Always track:

  • Unhandled exceptions
  • Critical business logic failures
  • External service failures

Don't track:

  • Expected validation errors (unless aggregating)
  • User cancellations
  • Rate limit hits (use metrics instead)

Anti-Patterns

❌ Swallowing Errors

// BAD: Silent failure
try {
  await saveToDatabase(data);
} catch (e) {
  // nothing
}

// GOOD: Handle or propagate
try {
  await saveToDatabase(data);
} catch (e) {
  logger.error({ error: e, data }, 'Failed to save');
  throw new DatabaseError('Save failed', { cause: e });
}

❌ Generic Catch-All

// BAD: Losing error type information
catch (e) {
  throw new Error('Something went wrong');
}

// GOOD: Preserve context
catch (e) {
  throw new AppError('Operation failed', 'OP_FAILED', { originalOp: 'createUser' }, e);
}

❌ Inconsistent Error Shapes

// BAD: Different error shapes across API
{ "error": "Invalid email" }           // endpoint 1
{ "message": "Not found", "code": 404 } // endpoint 2
{ "errors": ["Bad request"] }          // endpoint 3

// GOOD: Consistent shape everywhere
{ "error": { "code": "...", "message": "...", "requestId": "..." } }

Integration Points

With observability:

  • Include correlation IDs in all errors
  • Log errors with full context
  • Create alerts for error rate thresholds

With API design:

  • Use consistent error response shape
  • Map error types to HTTP status codes
  • Include request ID in responses

With testing:

  • Test error paths explicitly
  • Verify error boundary fallbacks
  • Mock failures in integration tests

Skill Usage

Designing error hierarchy:

  1. Read references/error-patterns.md
  2. Define base AppError with context support
  3. Create domain-specific error classes
  4. Establish error code registry

Adding React error boundaries:

  1. Read references/react-errors.md
  2. Implement error boundary component
  3. Add boundaries at appropriate levels
  4. Create fallback UIs

Implementing recovery strategies:

  1. Read references/recovery-patterns.md
  2. Add retry logic for transient failures
  3. Implement circuit breaker for unreliable services
  4. Design graceful degradation paths

Designing API error responses:

  1. Read references/api-errors.md
  2. Define consistent error response shape
  3. Implement centralized error handler
  4. Map error types to status codes

References:

スコア

総合スコア

60/100

リポジトリの品質指標に基づく評価

SKILL.md

SKILL.mdファイルが含まれている

+20
LICENSE

ライセンスが設定されている

0/10
説明文

100文字以上の説明がある

+10
人気

GitHub Stars 100以上

0/15
最近の活動

3ヶ月以内に更新がある

0/10
フォーク

10回以上フォークされている

0/5
Issue管理

オープンIssueが50未満

+5
言語

プログラミング言語が設定されている

+5
タグ

1つ以上のタグが設定されている

0/5

レビュー

💬

レビュー機能は近日公開予定です