Skip to main content

Overview

The SDK provides structured error handling with type-safe error classes, automatic retry logic for transient failures, and detailed error context for debugging.

Error hierarchy

All SDK errors extend SandboxError and include structured context:
try {
  await sandbox.exec('cat /nonexistent');
} catch (error) {
  if (error instanceof FileNotFoundError) {
    console.error('File not found:', error.path);
    console.error('Suggestion:', error.suggestion);
    console.error('HTTP Status:', error.httpStatus);
  }
}

Error structure

Every error includes:
  • code - Error code enum value (e.g., FILE_NOT_FOUND)
  • message - Human-readable error message
  • context - Type-safe context object with error details
  • httpStatus - HTTP status code from container API
  • operation - Operation that failed (e.g., file.read)
  • suggestion - Actionable suggestion to fix the error
  • timestamp - ISO 8601 timestamp when error occurred
  • documentation - Link to relevant documentation (when available)

Error categories

File system errors

FileNotFoundError - File or directory doesn’t exist:
try {
  const content = await sandbox.readFile('/missing.txt');
} catch (error) {
  if (error instanceof FileNotFoundError) {
    console.error('Path:', error.path); // "/missing.txt"
  }
}
FileExistsError - File already exists:
try {
  // Check if file exists before writing
  const exists = await sandbox.exists('/config.txt');
  if (exists) {
    console.log('File already exists');
  } else {
    await sandbox.writeFile('/config.txt', 'data');
  }
} catch (error) {
  if (error instanceof FileExistsError) {
    console.error('File exists:', error.path);
  }
}
FileTooLargeError - File exceeds size limits:
try {
  // Default max: 10MB for read operations
  await sandbox.readFile('/huge-file.bin');
} catch (error) {
  if (error instanceof FileTooLargeError) {
    console.error('File too large:', error.path);
    // Use streaming read instead
  }
}
PermissionDeniedError - Insufficient permissions:
try {
  await sandbox.exec('cat /etc/shadow');
} catch (error) {
  if (error instanceof PermissionDeniedError) {
    console.error('Permission denied:', error.path);
  }
}

Command errors

CommandNotFoundError - Command doesn’t exist:
try {
  await sandbox.exec('nonexistent-command');
} catch (error) {
  if (error instanceof CommandNotFoundError) {
    console.error('Command not found:', error.command);
    console.error('Suggestion:', error.suggestion); // "Install the package or check PATH"
  }
}
CommandError - Command failed with non-zero exit code:
try {
  await sandbox.exec('npm test');
} catch (error) {
  if (error instanceof CommandError) {
    console.error('Command:', error.command);
    console.error('Exit code:', error.exitCode);
    console.error('Stdout:', error.stdout);
    console.error('Stderr:', error.stderr);
  }
}

Process errors

ProcessNotFoundError - Process doesn’t exist:
try {
  await sandbox.killProcess('invalid-id');
} catch (error) {
  if (error instanceof ProcessNotFoundError) {
    console.error('Process not found:', error.processId);
  }
}
ProcessReadyTimeoutError - Process didn’t become ready in time:
try {
  await sandbox.startProcess({
    command: 'npm run dev',
    waitUntilReady: { timeout: 10000 }
  });
} catch (error) {
  if (error instanceof ProcessReadyTimeoutError) {
    console.error('Timeout after:', error.timeout, 'ms');
    console.error('Condition:', error.condition);
  }
}
ProcessExitedBeforeReadyError - Process crashed before becoming ready:
try {
  await sandbox.startProcess({
    command: 'npm run dev',
    waitUntilReady: { port: 3000 }
  });
} catch (error) {
  if (error instanceof ProcessExitedBeforeReadyError) {
    console.error('Process exited with code:', error.exitCode);
    console.error('Command:', error.command);
  }
}

Port errors

PortAlreadyExposedError - Port is already exposed:
try {
  await sandbox.exposePort(8080, 'app');
  await sandbox.exposePort(8080, 'api'); // Error!
} catch (error) {
  if (error instanceof PortAlreadyExposedError) {
    console.error('Port already exposed:', error.port);
    console.error('Existing name:', error.portName);
  }
}
PortNotExposedError - Port isn’t exposed:
try {
  await sandbox.unexposePort(9999);
} catch (error) {
  if (error instanceof PortNotExposedError) {
    console.error('Port not exposed:', error.port);
  }
}
InvalidPortError - Invalid port number:
try {
  await sandbox.exposePort(80); // Privileged port
} catch (error) {
  if (error instanceof InvalidPortError) {
    console.error('Invalid port:', error.port);
    console.error('Reason:', error.reason);
  }
}
ServiceNotRespondingError - Service on port isn’t responding:
try {
  await sandbox.exposePort(8080, 'api');
} catch (error) {
  if (error instanceof ServiceNotRespondingError) {
    console.error('Service not responding on port:', error.port);
    // Start the service first
  }
}

Git errors

GitRepositoryNotFoundError - Repository doesn’t exist:
try {
  await sandbox.git.clone('https://github.com/user/nonexistent');
} catch (error) {
  if (error instanceof GitRepositoryNotFoundError) {
    console.error('Repository not found:', error.repository);
  }
}
GitAuthenticationError - Git authentication failed:
try {
  await sandbox.git.clone('https://github.com/private/repo');
} catch (error) {
  if (error instanceof GitAuthenticationError) {
    console.error('Auth failed for:', error.repository);
    // Provide credentials
  }
}
GitBranchNotFoundError - Branch doesn’t exist:
try {
  await sandbox.git.clone('https://github.com/user/repo', {
    branch: 'nonexistent'
  });
} catch (error) {
  if (error instanceof GitBranchNotFoundError) {
    console.error('Branch not found:', error.branch);
    console.error('Repository:', error.repository);
  }
}

Backup errors

BackupNotFoundError - Backup doesn’t exist in R2:
try {
  await sandbox.restoreBackup('nonexistent-backup-id');
} catch (error) {
  if (error instanceof BackupNotFoundError) {
    console.error('Backup not found:', error.backupId);
  }
}
BackupExpiredError - Backup exceeded TTL:
try {
  await sandbox.restoreBackup('old-backup-id');
} catch (error) {
  if (error instanceof BackupExpiredError) {
    console.error('Backup expired at:', error.expiredAt);
  }
}

Automatic retry logic

The SDK automatically retries transient failures when starting containers.

HTTP status code semantics

StatusMeaningSDK Behavior
503Service Unavailable (container starting)Retry with exponential backoff
500Internal Server Error (config error)Fail immediately
400Bad Request (capacity limits, validation)Fail immediately

Retry configuration

Default settings:
  • Total budget: 2 minutes
  • Backoff: 3s → 6s → 12s → 24s → 30s (capped at 30s)
  • Only retries: 503 errors
Example retry sequence:
Attempt 1: 503 Service Unavailable → Wait 3s
Attempt 2: 503 Service Unavailable → Wait 6s
Attempt 3: 503 Service Unavailable → Wait 12s
Attempt 4: 200 OK → Success

Customize retry behavior

const sandbox = getSandbox(env.SANDBOX, 'my-sandbox', {
  containerTimeouts: {
    start: 120000, // 2 minute total budget
    connect: 30000 // 30 second connect timeout per attempt
  }
});

Capacity limit errors

When hitting account limits, the Containers API returns 400 with error codes:

Error codes

CodeDescriptionAction
SURPASSED_BASE_LIMITSExceeded per-deployment limitsScale down or contact support
SURPASSED_TOTAL_LIMITSExceeded account-wide limitsCall destroy() on unused sandboxes
LOCATION_SURPASSED_BASE_LIMITSExceeded location-specific limitsUse different location or scale down

Handling capacity errors

try {
  const sandbox = getSandbox(env.SANDBOX, `sandbox-${userId}`);
  await sandbox.exec('echo "test"');
} catch (error) {
  if (error.httpStatus === 400) {
    if (error.message.includes('SURPASSED_TOTAL_LIMITS')) {
      // Clean up old sandboxes
      await cleanupOldSandboxes(env);
      // Retry
    }
  }
}

Account limits (Workers Paid)

Default limits for Workers Paid plan:
ResourceLimit
Concurrent Memory400 GiB
Concurrent vCPU100
Concurrent Disk2 TB
See Containers limits for current values.

Error recovery patterns

Retry with exponential backoff

async function executeWithRetry(
  sandbox: Sandbox,
  command: string,
  maxRetries = 3
) {
  for (let i = 0; i < maxRetries; i++) {
    try {
      return await sandbox.exec(command);
    } catch (error) {
      if (i === maxRetries - 1) throw error;
      
      // Retry on transient errors
      if (error.httpStatus === 503) {
        const delay = Math.pow(2, i) * 1000; // 1s, 2s, 4s
        await new Promise(resolve => setTimeout(resolve, delay));
        continue;
      }
      
      throw error; // Don't retry permanent errors
    }
  }
}

Fallback to alternative approaches

async function readFileSafely(sandbox: Sandbox, path: string) {
  try {
    return await sandbox.readFile(path);
  } catch (error) {
    if (error instanceof FileTooLargeError) {
      // Fallback to streaming read
      const result = await sandbox.exec(`cat ${path}`);
      return result.stdout;
    }
    throw error;
  }
}

Graceful degradation

async function getMetrics(sandbox: Sandbox) {
  try {
    const result = await sandbox.exec('system-metrics');
    return JSON.parse(result.stdout);
  } catch (error) {
    if (error instanceof CommandNotFoundError) {
      // Fallback to basic metrics
      return { available: false };
    }
    throw error;
  }
}

Cleanup on error

async function processWithCleanup(sandbox: Sandbox) {
  let tempDir: string | null = null;
  
  try {
    // Create temp directory
    const result = await sandbox.exec('mktemp -d');
    tempDir = result.stdout.trim();
    
    // Do work
    await sandbox.exec(`process-data.sh ${tempDir}`);
  } catch (error) {
    console.error('Processing failed:', error.message);
    throw error;
  } finally {
    // Always cleanup
    if (tempDir) {
      await sandbox.exec(`rm -rf ${tempDir}`);
    }
  }
}

Best practices

Always check error types

// Good: Type-safe error handling
try {
  await sandbox.exec('command');
} catch (error) {
  if (error instanceof CommandNotFoundError) {
    // Handle missing command
  } else if (error instanceof PermissionDeniedError) {
    // Handle permission issues
  } else {
    // Handle unexpected errors
    throw error;
  }
}

// Bad: Generic error handling
try {
  await sandbox.exec('command');
} catch (error) {
  console.error('Something went wrong');
}

Use error context

try {
  await sandbox.readFile('/data/input.json');
} catch (error) {
  if (error instanceof FileNotFoundError) {
    // Use structured context
    console.error('File not found:', error.path);
    console.error('Suggestion:', error.suggestion);
    console.error('Operation:', error.operation);
    
    // Log full error for debugging
    console.error('Full error:', JSON.stringify(error.toJSON()));
  }
}

Don’t swallow errors

// Bad: Silent failure
try {
  await sandbox.exec('critical-command');
} catch (error) {
  // Error is lost!
}

// Good: Log and re-throw
try {
  await sandbox.exec('critical-command');
} catch (error) {
  console.error('Critical command failed:', error.message);
  throw error; // Re-throw for caller to handle
}

// Also good: Handle and continue
try {
  await sandbox.exec('optional-command');
} catch (error) {
  console.warn('Optional command failed, continuing:', error.message);
  // Continue execution
}

Set appropriate timeouts

// Configure timeouts based on operation type
const sandbox = getSandbox(env.SANDBOX, 'my-sandbox', {
  containerTimeouts: {
    start: 120000,   // 2 minutes for container startup
    connect: 30000,  // 30 seconds for initial connection
    request: 300000  // 5 minutes for long-running commands
  }
});

// Use command-level timeouts for specific operations
try {
  await sandbox.exec('long-running-task', {
    timeout: 600000 // 10 minutes
  });
} catch (error) {
  if (error.message.includes('timeout')) {
    console.error('Task timed out after 10 minutes');
  }
}

Monitor and alert

class ErrorMonitor {
  private errorCounts = new Map<string, number>();

  track(error: SandboxError) {
    const count = this.errorCounts.get(error.code) || 0;
    this.errorCounts.set(error.code, count + 1);

    // Alert on repeated errors
    if (count > 10) {
      this.alert(`High error rate for ${error.code}`);
    }
  }

  alert(message: string) {
    // Send to monitoring service
    console.error('ALERT:', message);
  }
}

const monitor = new ErrorMonitor();

try {
  await sandbox.exec('command');
} catch (error) {
  if (error instanceof SandboxError) {
    monitor.track(error);
  }
  throw error;
}

Debugging errors

Enable detailed logging

import { createLogger } from '@repo/shared';

const logger = createLogger({
  level: 'debug',
  component: 'sandbox'
});

const sandbox = getSandbox(env.SANDBOX, 'debug', {
  logger: logger
});

// Logs detailed request/response information
await sandbox.exec('echo "test"');

Inspect error objects

try {
  await sandbox.exec('failing-command');
} catch (error) {
  if (error instanceof SandboxError) {
    // Full error details
    console.error(JSON.stringify(error.toJSON(), null, 2));
    
    // Specific fields
    console.error('Code:', error.code);
    console.error('Operation:', error.operation);
    console.error('Context:', error.context);
    console.error('Stack:', error.stack);
  }
}

Capture error metrics

interface ErrorMetrics {
  timestamp: string;
  code: string;
  operation: string;
  duration: number;
}

const metrics: ErrorMetrics[] = [];

const start = Date.now();
try {
  await sandbox.exec('command');
} catch (error) {
  if (error instanceof SandboxError) {
    metrics.push({
      timestamp: new Date().toISOString(),
      code: error.code,
      operation: error.operation || 'unknown',
      duration: Date.now() - start
    });
  }
  throw error;
}

Common error scenarios

Container startup failures

try {
  const sandbox = getSandbox(env.SANDBOX, 'my-sandbox');
  await sandbox.exec('echo "test"');
} catch (error) {
  if (error.httpStatus === 500) {
    // Permanent failure - check configuration
    console.error('Container config error:', error.message);
  } else if (error.httpStatus === 503) {
    // Should have been retried automatically
    console.error('Container failed to start after retries');
  }
}

Resource exhaustion

try {
  await sandbox.exec('memory-intensive-task');
} catch (error) {
  if (error.message.includes('memory')) {
    console.error('Out of memory - reduce memory usage or scale up');
  }
}

Network failures

try {
  await sandbox.git.clone('https://github.com/user/repo');
} catch (error) {
  if (error instanceof GitNetworkError) {
    console.error('Network error:', error.message);
    // Retry with exponential backoff
  }
}

Permission issues

try {
  await sandbox.exec('systemctl restart nginx');
} catch (error) {
  if (error instanceof PermissionDeniedError) {
    console.error('Need root access - containers run as non-root user');
  }
}

Build docs developers (and LLMs) love