Error Recovery & Resilience Patterns

Overview

This guide provides comprehensive patterns for building resilient Claude Code workflows that can gracefully handle errors, recover from failures, and maintain continuity across interrupted sessions. These patterns help ensure robust automation and reliable task completion.

Table of Contents

  1. Session Recovery Patterns
  2. Multi-Tool Workflow Resilience
  3. Checkpoint and Rollback Strategies
  4. Error Handling in Complex Workflows
  5. Rate Limit and API Failure Recovery
  6. Context Window Recovery
  7. Tool-Specific Error Patterns
  8. Best Practices

Session Recovery Patterns

Basic Session Resumption

Claude Code supports session resumption using the --resume flag:

# Start a new session
claude-code "implement authentication system"
 
# If interrupted, resume the session
claude-code --resume
 
# Resume a specific session
claude-code --resume <session-id>

Handling Corrupted Sessions

When sessions encounter errors and become corrupted:

// Pattern: Session state validation
class SessionRecovery {
  async validateSession(sessionPath: string): Promise<boolean> {
    try {
      const sessionData = await readFile(sessionPath);
      const parsed = JSON.parse(sessionData);
      
      // Check for required fields
      const required = ['messages', 'context', 'timestamp'];
      return required.every(field => field in parsed);
      
    } catch (error) {
      console.error('Session validation failed:', error);
      return false;
    }
  }
  
  async recoverSession(sessionPath: string): Promise<Session | null> {
    const backupPath = `${sessionPath}.backup`;
    
    // Try primary session file
    if (await this.validateSession(sessionPath)) {
      return await this.loadSession(sessionPath);
    }
    
    // Try backup
    if (await this.validateSession(backupPath)) {
      console.log('Recovering from backup session');
      return await this.loadSession(backupPath);
    }
    
    // Extract salvageable data
    return await this.salvageSession(sessionPath);
  }
  
  async salvageSession(sessionPath: string): Promise<Session | null> {
    try {
      const raw = await readFile(sessionPath);
      const messages = this.extractMessages(raw);
      
      if (messages.length > 0) {
        return {
          messages,
          context: {},
          timestamp: new Date(),
          recovered: true
        };
      }
    } catch (error) {
      console.error('Session salvage failed:', error);
    }
    
    return null;
  }
}

Automatic Session Backup

Implement automatic session backups:

// Pattern: Periodic session backup
class SessionBackup {
  private backupInterval: NodeJS.Timer;
  private maxBackups = 5;
  
  startAutoBackup(sessionId: string, intervalMs = 300000) { // 5 minutes
    this.backupInterval = setInterval(() => {
      this.createBackup(sessionId);
    }, intervalMs);
  }
  
  async createBackup(sessionId: string): Promise<void> {
    const sessionPath = `~/.claude/sessions/${sessionId}`;
    const timestamp = new Date().toISOString().replace(/:/g, '-');
    const backupPath = `~/.claude/backups/${sessionId}-${timestamp}.json`;
    
    try {
      await copyFile(sessionPath, backupPath);
      await this.rotateBackups(sessionId);
      console.log(`Session backup created: ${backupPath}`);
    } catch (error) {
      console.error('Backup failed:', error);
    }
  }
  
  async rotateBackups(sessionId: string): Promise<void> {
    const backups = await glob(`~/.claude/backups/${sessionId}-*.json`);
    const sorted = backups.sort().reverse();
    
    // Remove old backups
    for (const backup of sorted.slice(this.maxBackups)) {
      await unlink(backup);
    }
  }
  
  stopAutoBackup(): void {
    if (this.backupInterval) {
      clearInterval(this.backupInterval);
    }
  }
}

Multi-Tool Workflow Resilience

Tool Execution with Retry Logic

Implement retry patterns for tool failures:

// Pattern: Resilient tool execution
class ResilientToolExecutor {
  async executeWithRetry<T>(
    toolName: string,
    params: any,
    options: {
      maxRetries?: number;
      retryDelay?: number;
      backoffMultiplier?: number;
      retryableErrors?: string[];
    } = {}
  ): Promise<T> {
    const {
      maxRetries = 3,
      retryDelay = 1000,
      backoffMultiplier = 2,
      retryableErrors = ['ETIMEDOUT', 'ECONNRESET', 'ENOTFOUND']
    } = options;
    
    let lastError: Error | null = null;
    let delay = retryDelay;
    
    for (let attempt = 1; attempt <= maxRetries; attempt++) {
      try {
        console.log(`Executing ${toolName} (attempt ${attempt}/${maxRetries})`);
        
        // Execute tool with timeout
        const result = await this.executeWithTimeout(
          toolName,
          params,
          30000 // 30 second timeout
        );
        
        return result;
        
      } catch (error) {
        lastError = error as Error;
        
        // Check if error is retryable
        const isRetryable = retryableErrors.some(
          errType => lastError!.message.includes(errType)
        );
        
        if (!isRetryable || attempt === maxRetries) {
          throw new Error(
            `Tool ${toolName} failed after ${attempt} attempts: ${lastError.message}`
          );
        }
        
        console.log(`Retrying in ${delay}ms...`);
        await this.sleep(delay);
        delay *= backoffMultiplier;
      }
    }
    
    throw lastError!;
  }
  
  private async executeWithTimeout<T>(
    toolName: string,
    params: any,
    timeoutMs: number
  ): Promise<T> {
    return Promise.race([
      this.executeTool(toolName, params),
      new Promise<never>((_, reject) => 
        setTimeout(() => reject(new Error(`Tool timeout: ${toolName}`)), timeoutMs)
      )
    ]);
  }
  
  private async executeTool(toolName: string, params: any): Promise<any> {
    // Tool execution logic here
    switch (toolName) {
      case 'bash':
        return await bash(params.command);
      case 'read':
        return await read(params.file_path);
      case 'grep':
        return await grep(params.pattern, params.options);
      default:
        throw new Error(`Unknown tool: ${toolName}`);
    }
  }
  
  private sleep(ms: number): Promise<void> {
    return new Promise(resolve => setTimeout(resolve, ms));
  }
}

Parallel Tool Execution with Fallbacks

Execute tools in parallel with fallback strategies:

// Pattern: Parallel execution with fallbacks
class ParallelToolExecutor {
  async executeParallelWithFallback(
    tasks: Array<{
      primary: { tool: string; params: any };
      fallback?: { tool: string; params: any };
    }>
  ): Promise<any[]> {
    const results = await Promise.allSettled(
      tasks.map(async task => {
        try {
          // Try primary tool
          return await this.executeTool(
            task.primary.tool,
            task.primary.params
          );
        } catch (primaryError) {
          console.error(`Primary tool failed: ${primaryError.message}`);
          
          // Try fallback if available
          if (task.fallback) {
            console.log(`Attempting fallback: ${task.fallback.tool}`);
            return await this.executeTool(
              task.fallback.tool,
              task.fallback.params
            );
          }
          
          throw primaryError;
        }
      })
    );
    
    // Process results
    return results.map((result, index) => {
      if (result.status === 'fulfilled') {
        return result.value;
      } else {
        console.error(`Task ${index} failed:`, result.reason);
        return { error: result.reason.message };
      }
    });
  }
  
  // Example usage
  async searchCodebase(query: string): Promise<any> {
    const searchTasks = [
      {
        primary: { 
          tool: 'grep', 
          params: { pattern: query, glob: '**/*.ts' } 
        },
        fallback: { 
          tool: 'task', 
          params: { 
            description: 'Code search',
            prompt: `Search for "${query}" in TypeScript files` 
          } 
        }
      },
      {
        primary: { 
          tool: 'grep', 
          params: { pattern: query, glob: '**/*.js' } 
        }
      }
    ];
    
    return await this.executeParallelWithFallback(searchTasks);
  }
}

Checkpoint and Rollback Strategies

Manual Checkpoint Creation

Implement checkpoint functionality:

// Pattern: Checkpoint management
class CheckpointManager {
  private checkpoints: Map<string, Checkpoint> = new Map();
  
  async createCheckpoint(
    name: string,
    description?: string
  ): Promise<string> {
    const checkpointId = `cp-${Date.now()}`;
    
    const checkpoint: Checkpoint = {
      id: checkpointId,
      name,
      description,
      timestamp: new Date(),
      files: await this.captureFileStates(),
      gitStatus: await this.captureGitStatus(),
      context: await this.captureContext()
    };
    
    this.checkpoints.set(checkpointId, checkpoint);
    await this.saveCheckpoint(checkpoint);
    
    console.log(`Checkpoint created: ${name} (${checkpointId})`);
    return checkpointId;
  }
  
  async rollbackToCheckpoint(checkpointId: string): Promise<void> {
    const checkpoint = this.checkpoints.get(checkpointId);
    if (!checkpoint) {
      throw new Error(`Checkpoint not found: ${checkpointId}`);
    }
    
    console.log(`Rolling back to checkpoint: ${checkpoint.name}`);
    
    // Restore file states
    for (const [filePath, content] of Object.entries(checkpoint.files)) {
      await write(filePath, content);
    }
    
    // Restore git state if needed
    if (checkpoint.gitStatus.hasChanges) {
      await bash('git stash');
      await bash(`git checkout ${checkpoint.gitStatus.branch}`);
    }
    
    console.log('Rollback completed');
  }
  
  private async captureFileStates(): Promise<Record<string, string>> {
    const files: Record<string, string> = {};
    const trackedFiles = await glob('**/*.{ts,js,json}', {
      ignore: ['node_modules/**', '.git/**']
    });
    
    for (const file of trackedFiles) {
      files[file] = await read(file);
    }
    
    return files;
  }
  
  private async captureGitStatus(): Promise<GitStatus> {
    const status = await bash('git status --porcelain');
    const branch = await bash('git branch --show-current');
    
    return {
      branch: branch.trim(),
      hasChanges: status.trim().length > 0,
      changes: status.trim().split('\n').filter(Boolean)
    };
  }
  
  private async captureContext(): Promise<any> {
    // Capture current Claude Code context
    return {
      workingDirectory: process.cwd(),
      environment: process.env.NODE_ENV,
      timestamp: new Date()
    };
  }
}

Automatic Checkpoint Strategy

Create checkpoints automatically at key moments:

// Pattern: Automatic checkpointing
class AutoCheckpoint {
  private checkpointManager: CheckpointManager;
  private config: AutoCheckpointConfig;
  
  constructor(config: AutoCheckpointConfig = {}) {
    this.checkpointManager = new CheckpointManager();
    this.config = {
      beforeMajorChanges: true,
      afterSuccessfulTests: true,
      onContextSwitch: true,
      intervalMinutes: 30,
      ...config
    };
    
    if (this.config.intervalMinutes) {
      this.startIntervalCheckpoints();
    }
  }
  
  async beforeMajorChange(description: string): Promise<void> {
    if (this.config.beforeMajorChanges) {
      await this.checkpointManager.createCheckpoint(
        `before-${description}`,
        `Automatic checkpoint before: ${description}`
      );
    }
  }
  
  async afterTests(testResults: TestResults): Promise<void> {
    if (this.config.afterSuccessfulTests && testResults.success) {
      await this.checkpointManager.createCheckpoint(
        'tests-passed',
        `All tests passing: ${testResults.passed}/${testResults.total}`
      );
    }
  }
  
  async onContextSwitch(fromContext: string, toContext: string): Promise<void> {
    if (this.config.onContextSwitch) {
      await this.checkpointManager.createCheckpoint(
        `context-switch`,
        `Switching from ${fromContext} to ${toContext}`
      );
    }
  }
  
  private startIntervalCheckpoints(): void {
    setInterval(async () => {
      await this.checkpointManager.createCheckpoint(
        'auto-interval',
        'Automatic interval checkpoint'
      );
    }, this.config.intervalMinutes! * 60 * 1000);
  }
}

Error Handling in Complex Workflows

Workflow State Machine

Implement state machine for complex workflows:

// Pattern: Resilient workflow state machine
class WorkflowStateMachine {
  private state: WorkflowState = 'idle';
  private stateHistory: StateTransition[] = [];
  private errorCount = 0;
  private maxErrors = 3;
  
  async executeWorkflow(
    steps: WorkflowStep[]
  ): Promise<WorkflowResult> {
    this.transitionTo('running');
    
    const results: StepResult[] = [];
    let currentStep = 0;
    
    while (currentStep < steps.length && this.state !== 'failed') {
      const step = steps[currentStep];
      
      try {
        this.transitionTo('executing', { step: step.name });
        
        const result = await this.executeStep(step);
        results.push({ step: step.name, success: true, result });
        
        currentStep++;
        this.errorCount = 0; // Reset error count on success
        
      } catch (error) {
        this.errorCount++;
        
        const handled = await this.handleStepError(
          step,
          error as Error,
          currentStep,
          steps
        );
        
        if (!handled) {
          this.transitionTo('failed', { error: error.message });
          break;
        }
        
        results.push({ 
          step: step.name, 
          success: false, 
          error: error.message,
          recovered: true 
        });
      }
    }
    
    const finalState = this.state === 'failed' ? 'failed' : 'completed';
    this.transitionTo(finalState);
    
    return {
      state: finalState,
      results,
      stateHistory: this.stateHistory
    };
  }
  
  private async executeStep(step: WorkflowStep): Promise<any> {
    // Add timeout protection
    const timeout = step.timeout || 60000;
    
    return Promise.race([
      step.execute(),
      new Promise((_, reject) => 
        setTimeout(() => reject(new Error('Step timeout')), timeout)
      )
    ]);
  }
  
  private async handleStepError(
    step: WorkflowStep,
    error: Error,
    currentIndex: number,
    allSteps: WorkflowStep[]
  ): Promise<boolean> {
    console.error(`Step "${step.name}" failed:`, error.message);
    
    // Check if we've exceeded max errors
    if (this.errorCount >= this.maxErrors) {
      console.error('Max errors exceeded');
      return false;
    }
    
    // Try error recovery strategies
    const strategies = [
      () => this.retryStep(step),
      () => this.tryAlternativeStep(step),
      () => this.skipStep(step),
      () => this.rollbackAndRetry(currentIndex, allSteps)
    ];
    
    for (const strategy of strategies) {
      try {
        const recovered = await strategy();
        if (recovered) {
          console.log('Error recovery successful');
          return true;
        }
      } catch (strategyError) {
        console.error('Recovery strategy failed:', strategyError.message);
      }
    }
    
    return false;
  }
  
  private async retryStep(step: WorkflowStep): Promise<boolean> {
    if (!step.retryable || (step.retryCount || 0) >= 3) {
      return false;
    }
    
    console.log(`Retrying step: ${step.name}`);
    step.retryCount = (step.retryCount || 0) + 1;
    
    await this.sleep(2000); // Wait before retry
    await step.execute();
    return true;
  }
  
  private transitionTo(
    newState: WorkflowState,
    metadata?: any
  ): void {
    this.stateHistory.push({
      from: this.state,
      to: newState,
      timestamp: new Date(),
      metadata
    });
    this.state = newState;
  }
}

Transaction Pattern for Multi-Step Operations

Implement transactional workflows:

// Pattern: Transactional workflow
class TransactionalWorkflow {
  private operations: Operation[] = [];
  private completedOps: CompletedOperation[] = [];
  
  async executeTransaction(
    operations: Operation[]
  ): Promise<TransactionResult> {
    this.operations = operations;
    this.completedOps = [];
    
    try {
      // Execute all operations
      for (const op of operations) {
        await this.executeOperation(op);
      }
      
      // All succeeded - commit
      await this.commit();
      
      return {
        success: true,
        operations: this.completedOps
      };
      
    } catch (error) {
      // Rollback on failure
      await this.rollback();
      
      return {
        success: false,
        error: error.message,
        rolledBack: true,
        operations: this.completedOps
      };
    }
  }
  
  private async executeOperation(op: Operation): Promise<void> {
    console.log(`Executing: ${op.name}`);
    
    // Capture state before operation
    const beforeState = await op.captureState?.();
    
    // Execute operation
    const result = await op.execute();
    
    // Record completed operation
    this.completedOps.push({
      operation: op,
      result,
      beforeState,
      timestamp: new Date()
    });
  }
  
  private async rollback(): Promise<void> {
    console.log('Rolling back transaction...');
    
    // Rollback in reverse order
    for (const completed of this.completedOps.reverse()) {
      if (completed.operation.rollback) {
        try {
          await completed.operation.rollback(completed.beforeState);
          console.log(`Rolled back: ${completed.operation.name}`);
        } catch (error) {
          console.error(`Rollback failed for ${completed.operation.name}:`, error);
        }
      }
    }
  }
  
  private async commit(): Promise<void> {
    console.log('Committing transaction...');
    
    // Execute commit hooks
    for (const completed of this.completedOps) {
      if (completed.operation.commit) {
        await completed.operation.commit(completed.result);
      }
    }
  }
}
 
// Example usage
const workflow = new TransactionalWorkflow();
 
await workflow.executeTransaction([
  {
    name: 'Create database schema',
    execute: async () => await bash('npm run db:migrate'),
    rollback: async () => await bash('npm run db:rollback'),
    captureState: async () => await bash('npm run db:schema:dump')
  },
  {
    name: 'Deploy application',
    execute: async () => await bash('npm run deploy'),
    rollback: async (state) => await bash('npm run deploy:rollback')
  },
  {
    name: 'Run smoke tests',
    execute: async () => await bash('npm run test:smoke'),
    rollback: async () => console.log('No rollback needed for tests')
  }
]);

Rate Limit and API Failure Recovery

Adaptive Rate Limiting

Implement adaptive rate limiting:

// Pattern: Adaptive rate limiter
class AdaptiveRateLimiter {
  private requestTimes: number[] = [];
  private errorCounts = new Map<string, number>();
  private backoffMultipliers = new Map<string, number>();
  
  async executeWithRateLimit<T>(
    operation: () => Promise<T>,
    operationId: string,
    options: RateLimitOptions = {}
  ): Promise<T> {
    const {
      minDelay = 100,
      maxDelay = 60000,
      targetRPM = 60
    } = options;
    
    // Calculate current delay
    const currentDelay = this.calculateDelay(
      operationId,
      minDelay,
      maxDelay,
      targetRPM
    );
    
    // Wait if necessary
    if (currentDelay > 0) {
      console.log(`Rate limiting: waiting ${currentDelay}ms`);
      await this.sleep(currentDelay);
    }
    
    // Record request time
    this.requestTimes.push(Date.now());
    this.cleanOldRequests();
    
    try {
      const result = await operation();
      
      // Success - reduce backoff
      this.onSuccess(operationId);
      return result;
      
    } catch (error) {
      // Failure - increase backoff
      this.onError(operationId, error as Error);
      throw error;
    }
  }
  
  private calculateDelay(
    operationId: string,
    minDelay: number,
    maxDelay: number,
    targetRPM: number
  ): number {
    // Get backoff multiplier
    const multiplier = this.backoffMultipliers.get(operationId) || 1;
    
    // Calculate base delay from current rate
    const currentRPM = this.getCurrentRPM();
    const baseDelay = currentRPM > targetRPM 
      ? (60000 / targetRPM) - (60000 / currentRPM)
      : 0;
    
    // Apply backoff multiplier
    const delay = Math.min(baseDelay * multiplier, maxDelay);
    
    return Math.max(delay, minDelay);
  }
  
  private getCurrentRPM(): number {
    const oneMinuteAgo = Date.now() - 60000;
    const recentRequests = this.requestTimes.filter(t => t > oneMinuteAgo);
    return recentRequests.length;
  }
  
  private onSuccess(operationId: string): void {
    // Reset error count
    this.errorCounts.delete(operationId);
    
    // Reduce backoff multiplier
    const current = this.backoffMultipliers.get(operationId) || 1;
    this.backoffMultipliers.set(operationId, Math.max(current * 0.9, 1));
  }
  
  private onError(operationId: string, error: Error): void {
    // Increment error count
    const count = (this.errorCounts.get(operationId) || 0) + 1;
    this.errorCounts.set(operationId, count);
    
    // Increase backoff if rate limit error
    if (error.message.includes('429') || error.message.includes('rate limit')) {
      const current = this.backoffMultipliers.get(operationId) || 1;
      this.backoffMultipliers.set(operationId, Math.min(current * 2, 10));
    }
  }
  
  private cleanOldRequests(): void {
    const oneMinuteAgo = Date.now() - 60000;
    this.requestTimes = this.requestTimes.filter(t => t > oneMinuteAgo);
  }
  
  private sleep(ms: number): Promise<void> {
    return new Promise(resolve => setTimeout(resolve, ms));
  }
}

Context Window Recovery

Context Optimization on Overflow

Handle context window overflow gracefully:

// Pattern: Context window management
class ContextWindowManager {
  private contextUsage = 0;
  private maxContext = 100; // percentage
  private warningThreshold = 80;
  private criticalThreshold = 95;
  
  async manageContext(
    operation: () => Promise<any>
  ): Promise<any> {
    // Check context before operation
    this.contextUsage = await this.getContextUsage();
    
    if (this.contextUsage >= this.criticalThreshold) {
      console.log('Critical context usage detected');
      await this.emergencyCompact();
    } else if (this.contextUsage >= this.warningThreshold) {
      console.log('High context usage - preparing for compaction');
      await this.smartCompact();
    }
    
    try {
      return await operation();
    } catch (error) {
      if (this.isContextError(error)) {
        console.log('Context overflow detected - recovering');
        await this.recoverFromContextOverflow();
        
        // Retry with reduced context
        return await operation();
      }
      throw error;
    }
  }
  
  private async emergencyCompact(): Promise<void> {
    console.log('Performing emergency context compaction');
    
    // Use /compact command
    await this.executeCompact();
    
    // Wait for compaction to complete
    await this.sleep(2000);
    
    // Verify context reduction
    const newUsage = await this.getContextUsage();
    console.log(`Context reduced from ${this.contextUsage}% to ${newUsage}%`);
  }
  
  private async smartCompact(): Promise<void> {
    // Intelligent compaction - preserve important context
    const important = await this.identifyImportantContext();
    
    console.log('Performing smart compaction');
    await this.executeCompact();
    
    // Re-add important context if needed
    if (important.length > 0) {
      console.log('Restoring important context');
      // Re-read important files or re-establish context
    }
  }
  
  private async recoverFromContextOverflow(): Promise<void> {
    // Clear context completely
    await this.clearContext();
    
    // Restore minimal working context
    await this.restoreMinimalContext();
  }
  
  private isContextError(error: any): boolean {
    const contextErrors = [
      'context_length_exceeded',
      'token_limit_exceeded',
      'context window',
      'maximum context'
    ];
    
    return contextErrors.some(err => 
      error.message?.toLowerCase().includes(err)
    );
  }
  
  private async getContextUsage(): Promise<number> {
    // Implementation would check actual context usage
    // This is a placeholder
    return Math.random() * 100;
  }
  
  private async executeCompact(): Promise<void> {
    // Execute /compact command
    console.log('Executing /compact command');
  }
  
  private sleep(ms: number): Promise<void> {
    return new Promise(resolve => setTimeout(resolve, ms));
  }
}

Tool-Specific Error Patterns

Bash Tool Error Recovery

Handle bash command failures:

// Pattern: Resilient bash execution
class ResilientBashExecutor {
  async execute(
    command: string,
    options: BashOptions = {}
  ): Promise<BashResult> {
    const {
      retries = 3,
      timeout = 120000,
      errorPatterns = this.getDefaultErrorPatterns(),
      recoveryStrategies = this.getDefaultRecoveryStrategies()
    } = options;
    
    let lastError: Error | null = null;
    
    for (let attempt = 1; attempt <= retries; attempt++) {
      try {
        // Set timeout for bash command
        const result = await this.executeWithTimeout(command, timeout);
        
        // Check for error patterns in output
        const errorPattern = this.checkErrorPatterns(
          result.output,
          errorPatterns
        );
        
        if (errorPattern) {
          throw new Error(`Command failed: ${errorPattern.message}`);
        }
        
        return result;
        
      } catch (error) {
        lastError = error as Error;
        console.error(`Bash command failed (attempt ${attempt}):`, error.message);
        
        // Try recovery strategy
        const recovered = await this.tryRecovery(
          command,
          error as Error,
          recoveryStrategies
        );
        
        if (recovered) {
          command = recovered; // Use modified command
          continue;
        }
        
        if (attempt < retries) {
          await this.sleep(1000 * attempt); // Exponential backoff
        }
      }
    }
    
    throw new Error(`Bash command failed after ${retries} attempts: ${lastError?.message}`);
  }
  
  private getDefaultErrorPatterns(): ErrorPattern[] {
    return [
      {
        pattern: /EACCES|Permission denied/i,
        message: 'Permission denied',
        recoverable: true
      },
      {
        pattern: /ENOENT|No such file/i,
        message: 'File not found',
        recoverable: true
      },
      {
        pattern: /EADDRINUSE|already in use/i,
        message: 'Port already in use',
        recoverable: true
      }
    ];
  }
  
  private getDefaultRecoveryStrategies(): RecoveryStrategy[] {
    return [
      {
        errorPattern: /Permission denied/i,
        recover: (cmd) => `sudo ${cmd}`
      },
      {
        errorPattern: /npm: command not found/i,
        recover: () => 'which npm || echo "npm not found"'
      },
      {
        errorPattern: /Port.*already in use/i,
        recover: (cmd) => {
          const port = cmd.match(/port[= ](\d+)/i)?.[1] || '3000';
          return `lsof -ti:${port} | xargs kill -9 && ${cmd}`;
        }
      }
    ];
  }
  
  private async tryRecovery(
    command: string,
    error: Error,
    strategies: RecoveryStrategy[]
  ): Promise<string | null> {
    for (const strategy of strategies) {
      if (strategy.errorPattern.test(error.message)) {
        console.log('Applying recovery strategy');
        return strategy.recover(command);
      }
    }
    return null;
  }
}

File Operation Error Recovery

Handle file system errors:

// Pattern: Resilient file operations
class ResilientFileOperations {
  async readWithRecovery(
    filePath: string,
    options: FileOptions = {}
  ): Promise<string> {
    const {
      encoding = 'utf-8',
      createIfMissing = false,
      defaultContent = '',
      backupPaths = []
    } = options;
    
    // Try primary path
    try {
      return await read(filePath);
    } catch (error) {
      if (error.code === 'ENOENT') {
        // Try backup paths
        for (const backup of backupPaths) {
          try {
            console.log(`Trying backup: ${backup}`);
            return await read(backup);
          } catch (backupError) {
            continue;
          }
        }
        
        // Create if missing
        if (createIfMissing) {
          console.log(`Creating missing file: ${filePath}`);
          await write(filePath, defaultContent);
          return defaultContent;
        }
      }
      
      throw error;
    }
  }
  
  async writeWithBackup(
    filePath: string,
    content: string
  ): Promise<void> {
    const backupPath = `${filePath}.backup`;
    
    try {
      // Create backup of existing file
      if (await this.fileExists(filePath)) {
        const existing = await read(filePath);
        await write(backupPath, existing);
      }
      
      // Write new content
      await write(filePath, content);
      
    } catch (error) {
      // Restore from backup on failure
      if (await this.fileExists(backupPath)) {
        console.error('Write failed, restoring backup');
        const backup = await read(backupPath);
        await write(filePath, backup);
      }
      throw error;
    }
  }
  
  private async fileExists(path: string): Promise<boolean> {
    try {
      await read(path);
      return true;
    } catch (error) {
      return false;
    }
  }
}

Best Practices

1. Defensive Programming

// Always validate inputs and outputs
async function safeOperation(input: any): Promise<Result> {
  // Validate input
  if (!input || typeof input !== 'object') {
    throw new Error('Invalid input');
  }
  
  try {
    const result = await riskyOperation(input);
    
    // Validate output
    if (!result || !result.success) {
      throw new Error('Invalid operation result');
    }
    
    return result;
  } catch (error) {
    // Log and re-throw with context
    console.error('Operation failed:', {
      input,
      error: error.message,
      stack: error.stack
    });
    throw error;
  }
}

2. Progressive Enhancement

// Start simple, add complexity as needed
class ProgressiveWorkflow {
  async execute(complexity: 'simple' | 'normal' | 'complex'): Promise<any> {
    switch (complexity) {
      case 'simple':
        return await this.simpleWorkflow();
      
      case 'normal':
        return await this.normalWorkflow();
      
      case 'complex':
        return await this.complexWorkflow();
    }
  }
  
  private async simpleWorkflow(): Promise<any> {
    // Basic operations with minimal error handling
    return await this.basicOperation();
  }
  
  private async normalWorkflow(): Promise<any> {
    // Add retries and basic recovery
    const executor = new ResilientToolExecutor();
    return await executor.executeWithRetry('operation', {});
  }
  
  private async complexWorkflow(): Promise<any> {
    // Full state machine with checkpoints
    const workflow = new WorkflowStateMachine();
    const checkpoint = new CheckpointManager();
    
    await checkpoint.createCheckpoint('workflow-start');
    return await workflow.executeWorkflow(this.getSteps());
  }
}

3. Monitoring and Alerting

// Track errors and performance
class ErrorMonitor {
  private metrics = {
    errors: new Map<string, number>(),
    recoveries: new Map<string, number>(),
    performance: new Map<string, number[]>()
  };
  
  recordError(type: string, error: Error): void {
    const count = this.metrics.errors.get(type) || 0;
    this.metrics.errors.set(type, count + 1);
    
    // Alert on threshold
    if (count > 10) {
      console.warn(`High error rate for ${type}: ${count} errors`);
    }
  }
  
  recordRecovery(type: string): void {
    const count = this.metrics.recoveries.get(type) || 0;
    this.metrics.recoveries.set(type, count + 1);
  }
  
  getHealthStatus(): HealthStatus {
    const totalErrors = Array.from(this.metrics.errors.values())
      .reduce((sum, count) => sum + count, 0);
    
    const totalRecoveries = Array.from(this.metrics.recoveries.values())
      .reduce((sum, count) => sum + count, 0);
    
    const recoveryRate = totalErrors > 0 
      ? totalRecoveries / totalErrors 
      : 1;
    
    return {
      healthy: recoveryRate > 0.8,
      errorCount: totalErrors,
      recoveryCount: totalRecoveries,
      recoveryRate
    };
  }
}

4. Graceful Degradation

## Degradation Strategy
 
1. **Full Feature Set** → Try complete operation
2. **Reduced Features** → Disable non-essential features
3. **Basic Operation** → Core functionality only
4. **Safe Mode** → Minimal viable operation
5. **Offline Mode** → Local operations only

5. Documentation and Logging

// Comprehensive error documentation
class DocumentedError extends Error {
  constructor(
    message: string,
    public code: string,
    public context: any,
    public recovery?: string
  ) {
    super(message);
    this.name = 'DocumentedError';
  }
  
  toJSON() {
    return {
      name: this.name,
      message: this.message,
      code: this.code,
      context: this.context,
      recovery: this.recovery,
      timestamp: new Date().toISOString()
    };
  }
}
 
// Usage
throw new DocumentedError(
  'Database connection failed',
  'DB_CONN_FAILED',
  { host: 'localhost', port: 5432 },
  'Check database server is running and credentials are correct'
);

Summary

Building resilient Claude Code workflows requires:

  1. Proactive error handling with retry logic and fallbacks
  2. State management through checkpoints and rollback capabilities
  3. Graceful degradation when facing resource constraints
  4. Comprehensive monitoring to track and improve reliability
  5. Context awareness to handle Claude Code-specific limitations

By implementing these patterns, you can create robust automations that handle failures gracefully and maintain productivity even in challenging conditions.

Error Handling & Recovery

Multi-Agent & Workflow Orchestration

Security & Compliance

Deployment & Operations

References