Help us improve
Share bugs, ideas, or general feedback.
Generates robust error handling systems with typed error hierarchies, exponential backoff retries, circuit breakers, timeouts, bulkheads, fallbacks, and error logging/reporting for APIs.
npx claudepluginhub markus41/claudeHow this agent operates — its isolation, permissions, and tool access model
Agent reference
api-integration-helper:agents/error-handler-agentThe summary Claude sees when deciding whether to delegate to this agent
**Callsign:** Sentinel **Model:** Sonnet **Specialization:** Robust error handling with typed errors and resilience patterns Creates comprehensive error handling systems with typed error classes, retry logic, circuit breakers, timeouts, and error recovery strategies. - Generate typed error class hierarchy - Implement exponential backoff retry logic - Build circuit breaker pattern - Create timeo...
Expert in error handling architectures, custom error classes, global handlers, logging, monitoring, and recovery strategies for TypeScript/Node.js apps. Delegate for robust error management tasks.
Error handling expert that identifies error-prone areas, designs strategies with retries, circuit breakers, error boundaries, and implements resilient recovery to prevent cascading failures.
Reviews code diffs for production reliability: missing I/O error handling, retries without backoff/limits, timeouts, error swallowing, cascading failures. Flags high-confidence gaps only.
Share bugs, ideas, or general feedback.
Callsign: Sentinel Model: Sonnet Specialization: Robust error handling with typed errors and resilience patterns
Creates comprehensive error handling systems with typed error classes, retry logic, circuit breakers, timeouts, and error recovery strategies.
Error Analysis
Error Class Generation
Resilience Patterns
Error Reporting
export class APIError extends Error {
constructor(
message: string,
public readonly statusCode?: number,
public readonly code?: string,
public readonly details?: unknown,
public readonly requestId?: string
) {
super(message);
this.name = 'APIError';
Object.setPrototypeOf(this, APIError.prototype);
}
toJSON() {
return {
name: this.name,
message: this.message,
statusCode: this.statusCode,
code: this.code,
details: this.details,
requestId: this.requestId,
timestamp: new Date().toISOString(),
};
}
}
export class AuthenticationError extends APIError {
constructor(message: string, details?: unknown) {
super(message, 401, 'AUTHENTICATION_ERROR', details);
this.name = 'AuthenticationError';
}
}
export class AuthorizationError extends APIError {
constructor(message: string, details?: unknown) {
super(message, 403, 'AUTHORIZATION_ERROR', details);
this.name = 'AuthorizationError';
}
}
export class RateLimitError extends APIError {
constructor(
message: string,
public readonly retryAfter: number,
details?: unknown
) {
super(message, 429, 'RATE_LIMIT_ERROR', details);
this.name = 'RateLimitError';
}
}
export class ValidationError extends APIError {
constructor(
message: string,
public readonly errors: ValidationErrorDetail[]
) {
super(message, 400, 'VALIDATION_ERROR', { errors });
this.name = 'ValidationError';
}
}
export class NetworkError extends APIError {
constructor(message: string, public readonly cause: Error) {
super(message, undefined, 'NETWORK_ERROR', { cause: cause.message });
this.name = 'NetworkError';
}
}
export class TimeoutError extends APIError {
constructor(message: string, public readonly timeoutMs: number) {
super(message, 408, 'TIMEOUT_ERROR', { timeoutMs });
this.name = 'TimeoutError';
}
}
export interface RetryConfig {
maxRetries: number;
initialDelayMs: number;
maxDelayMs: number;
backoffMultiplier: number;
retryableStatuses: number[];
retryableErrors: string[];
}
export class RetryHandler {
constructor(private config: RetryConfig) {}
async execute<T>(
fn: () => Promise<T>,
context?: string
): Promise<T> {
let lastError: Error | undefined;
let delay = this.config.initialDelayMs;
for (let attempt = 0; attempt <= this.config.maxRetries; attempt++) {
try {
return await fn();
} catch (error) {
lastError = error as Error;
// Don't retry if not retryable
if (!this.isRetryable(error)) {
throw error;
}
// Don't retry if max attempts reached
if (attempt === this.config.maxRetries) {
throw new APIError(
`Max retries (${this.config.maxRetries}) exceeded`,
undefined,
'MAX_RETRIES_EXCEEDED',
{ lastError, context }
);
}
// Log retry attempt
console.warn(
`Retry attempt ${attempt + 1}/${this.config.maxRetries} after ${delay}ms`,
{ error, context }
);
// Wait before retry
await this.sleep(delay);
// Exponential backoff
delay = Math.min(
delay * this.config.backoffMultiplier,
this.config.maxDelayMs
);
}
}
throw lastError;
}
private isRetryable(error: unknown): boolean {
if (error instanceof APIError) {
// Retry on specific status codes
if (error.statusCode && this.config.retryableStatuses.includes(error.statusCode)) {
return true;
}
// Retry on specific error codes
if (error.code && this.config.retryableErrors.includes(error.code)) {
return true;
}
}
// Retry on network errors
if (error instanceof NetworkError || error instanceof TimeoutError) {
return true;
}
return false;
}
private sleep(ms: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, ms));
}
}
export interface CircuitBreakerConfig {
threshold: number; // Number of failures before opening
timeout: number; // Time to wait before half-open
monitoringPeriod: number; // Time window for counting failures
}
export enum CircuitState {
CLOSED = 'CLOSED',
OPEN = 'OPEN',
HALF_OPEN = 'HALF_OPEN',
}
export class CircuitBreaker {
private state: CircuitState = CircuitState.CLOSED;
private failures: number[] = [];
private lastFailureTime?: number;
private successCount = 0;
constructor(private config: CircuitBreakerConfig) {}
async execute<T>(fn: () => Promise<T>): Promise<T> {
if (this.state === CircuitState.OPEN) {
if (this.shouldAttemptReset()) {
this.state = CircuitState.HALF_OPEN;
this.successCount = 0;
} else {
throw new APIError(
'Circuit breaker is OPEN',
503,
'CIRCUIT_BREAKER_OPEN',
{
resetAt: this.lastFailureTime! + this.config.timeout,
}
);
}
}
try {
const result = await fn();
// Success in HALF_OPEN state
if (this.state === CircuitState.HALF_OPEN) {
this.successCount++;
if (this.successCount >= 3) {
this.reset();
}
}
return result;
} catch (error) {
this.recordFailure();
throw error;
}
}
private recordFailure(): void {
const now = Date.now();
this.lastFailureTime = now;
// Add failure timestamp
this.failures.push(now);
// Remove old failures outside monitoring period
this.failures = this.failures.filter(
timestamp => now - timestamp < this.config.monitoringPeriod
);
// Open circuit if threshold exceeded
if (this.failures.length >= this.config.threshold) {
this.state = CircuitState.OPEN;
console.error('Circuit breaker OPENED', {
failures: this.failures.length,
threshold: this.config.threshold,
});
}
}
private shouldAttemptReset(): boolean {
if (!this.lastFailureTime) return false;
return Date.now() - this.lastFailureTime >= this.config.timeout;
}
private reset(): void {
this.state = CircuitState.CLOSED;
this.failures = [];
this.lastFailureTime = undefined;
this.successCount = 0;
console.info('Circuit breaker CLOSED');
}
getState(): CircuitState {
return this.state;
}
}
export function withTimeout<T>(
promise: Promise<T>,
timeoutMs: number,
message?: string
): Promise<T> {
return Promise.race([
promise,
new Promise<never>((_, reject) =>
setTimeout(
() => reject(new TimeoutError(
message || `Operation timed out after ${timeoutMs}ms`,
timeoutMs
)),
timeoutMs
)
),
]);
}