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
- Session Recovery Patterns
- Multi-Tool Workflow Resilience
- Checkpoint and Rollback Strategies
- Error Handling in Complex Workflows
- Rate Limit and API Failure Recovery
- Context Window Recovery
- Tool-Specific Error Patterns
- 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 only5. 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:
- Proactive error handling with retry logic and fallbacks
- State management through checkpoints and rollback capabilities
- Graceful degradation when facing resource constraints
- Comprehensive monitoring to track and improve reliability
- 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.
Related Patterns and Concepts
Error Handling & Recovery
- Testing Patterns - Test-driven approaches to error prevention
- Troubleshooting Guide - Debugging techniques for error resolution
- Basic Memory Troubleshooting - Real-world example of error recovery
Multi-Agent & Workflow Orchestration
- Multi-Agent Workflow Orchestration - Building resilient multi-agent systems
- Hook Patterns - Error handling in hook-based architectures
- Performance Optimization - Preventing performance-related failures
Security & Compliance
- Security Deep Dive - Security considerations for resilient systems
- Runtime Security Patterns - Real-time error detection and response
- Observability Patterns - Monitoring for early error detection
Deployment & Operations
- CD Automation - Automated testing and rollback strategies
- Kubernetes Deployment - Container orchestration for resilience
- Database Migrations - Transactional patterns for data operations