|
| 1 | +# CLAUDE.md |
| 2 | + |
| 3 | +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. |
| 4 | + |
| 5 | +## Documentation Resources |
| 6 | + |
| 7 | +**Always consult the Cloudflare Docs MCP when working on this repository.** The MCP provides comprehensive documentation about: |
| 8 | +- API usage patterns and examples |
| 9 | +- Architecture concepts and best practices |
| 10 | +- Configuration reference (wrangler, Dockerfile) |
| 11 | +- Troubleshooting guides |
| 12 | +- Production deployment requirements |
| 13 | + |
| 14 | +Use the MCP tools (e.g., `mcp__cloudflare-docs__search_cloudflare_documentation`) to search for specific topics before making changes. |
| 15 | + |
| 16 | +**Exa MCP is available for code search.** Use the exa-code tool when you need real-world code examples or patterns from GitHub repositories, documentation, or Stack Overflow to inform your implementation decisions and avoid hallucinations. |
| 17 | + |
| 18 | +**Always use the `gh` CLI for GitHub interactions.** When you need to access GitHub issues, PRs, repository information, or any GitHub-related data, use the gh CLI tool (e.g., `gh issue view`, `gh pr view`, `gh repo view`) instead of trying to fetch GitHub URLs directly. The CLI provides structured, reliable output and better access to GitHub data. |
| 19 | + |
| 20 | +## Project Overview |
| 21 | + |
| 22 | +The Cloudflare Sandbox SDK enables secure, isolated code execution in containers running on Cloudflare. The SDK allows Workers to execute arbitrary commands, manage files, run background processes, and expose services. |
| 23 | + |
| 24 | +**Status**: Open Beta - API is stable but may evolve based on feedback. Safe for production use. |
| 25 | + |
| 26 | +## Architecture |
| 27 | + |
| 28 | +### Three-Layer Architecture |
| 29 | + |
| 30 | +1. **`@cloudflare/sandbox` (packages/sandbox/)** - Public SDK exported to npm |
| 31 | + - `Sandbox` class: Durable Object that manages the container lifecycle |
| 32 | + - Client architecture: Modular HTTP clients for different capabilities (CommandClient, FileClient, ProcessClient, etc.) |
| 33 | + - `CodeInterpreter`: High-level API for running Python/JavaScript with structured outputs |
| 34 | + - `proxyToSandbox()`: Request handler for preview URL routing |
| 35 | + |
| 36 | +2. **`@repo/shared` (packages/shared/)** - Shared types and error system |
| 37 | + - Type definitions shared between SDK and container runtime |
| 38 | + - Centralized error handling and logging utilities |
| 39 | + - Not published to npm (internal workspace package) |
| 40 | + |
| 41 | +3. **`@repo/sandbox-container` (packages/sandbox-container/)** - Container runtime |
| 42 | + - Bun-based HTTP server running inside Docker containers |
| 43 | + - Dependency injection container (`core/container.ts`) |
| 44 | + - Route handlers for command execution, file operations, process management |
| 45 | + - Not published to npm (bundled into Docker image) |
| 46 | + |
| 47 | +### Key Flow |
| 48 | + |
| 49 | +Worker → Sandbox DO → Container HTTP API (port 3000) → Bun runtime → Shell commands/File system |
| 50 | + |
| 51 | +## Development Commands |
| 52 | + |
| 53 | +### Building |
| 54 | + |
| 55 | +```bash |
| 56 | +npm run build # Build all packages (uses turbo) |
| 57 | +npm run build:clean # Force rebuild without cache |
| 58 | +``` |
| 59 | + |
| 60 | +### Testing |
| 61 | + |
| 62 | +```bash |
| 63 | +# Unit tests (runs in Workers runtime with vitest-pool-workers) |
| 64 | +npm test |
| 65 | + |
| 66 | +# E2E tests (requires Docker, runs sequentially due to container provisioning) |
| 67 | +npm run test:e2e |
| 68 | + |
| 69 | +# Run a single E2E test file |
| 70 | +npm run test:e2e -- -- tests/e2e/process-lifecycle-workflow.test.ts |
| 71 | + |
| 72 | +# Run a specific test within a file |
| 73 | +npm run test:e2e -- -- tests/e2e/git-clone-workflow.test.ts -t 'test name' |
| 74 | +``` |
| 75 | + |
| 76 | +**Important**: E2E tests (`tests/e2e/`) run sequentially (not in parallel) to avoid container resource contention. Each test spawns its own wrangler dev instance. |
| 77 | + |
| 78 | +### Code Quality |
| 79 | + |
| 80 | +```bash |
| 81 | +npm run check # Run Biome linter + typecheck |
| 82 | +npm run fix # Auto-fix linting issues + typecheck |
| 83 | +npm run typecheck # TypeScript type checking only |
| 84 | +``` |
| 85 | + |
| 86 | +### Docker |
| 87 | + |
| 88 | +Docker builds are typically **automated via CI**, but you can build locally for testing: |
| 89 | + |
| 90 | +```bash |
| 91 | +npm run docker:rebuild # Rebuild container image locally (includes clean build + Docker) |
| 92 | +``` |
| 93 | + |
| 94 | +**Note:** Docker images are automatically built and published by CI (`release.yml`): |
| 95 | +- Beta images on every main commit |
| 96 | +- Stable images when "Version Packages" PR is merged |
| 97 | +- Multi-arch builds (amd64, arm64) handled by CI |
| 98 | + |
| 99 | +**Critical: ** Docker image version MUST match npm package version ( `@cloudflare/[email protected]` → `cloudflare/sandbox:0.4.7`). This is enforced via `ARG SANDBOX_VERSION` in Dockerfile. |
| 100 | + |
| 101 | +### Development Server |
| 102 | + |
| 103 | +From an example directory (e.g., `examples/minimal/`): |
| 104 | +```bash |
| 105 | +npm run dev # Start wrangler dev server (builds Docker on first run) |
| 106 | +``` |
| 107 | + |
| 108 | +**Local development gotcha**: When testing port exposure with `wrangler dev`, the Dockerfile must include `EXPOSE` directives for those ports. Without `EXPOSE`, you'll see "Connection refused: container port not found". This is only required for local dev - production automatically makes all ports accessible. |
| 109 | + |
| 110 | +## Development Workflow |
| 111 | + |
| 112 | +**Main branch is protected.** All changes must go through pull requests. The CI pipeline runs comprehensive tests on every PR - these MUST pass before merging. |
| 113 | + |
| 114 | +### Pull Request Process |
| 115 | + |
| 116 | +1. Make your changes |
| 117 | + |
| 118 | +2. **Run code quality checks after any meaningful change:** |
| 119 | + ```bash |
| 120 | + npm run check # Runs Biome linter + typecheck |
| 121 | + ``` |
| 122 | + This catches type errors that often expose real issues with code changes. Fix any issues before proceeding. |
| 123 | + |
| 124 | +3. **Run unit tests to verify your changes:** |
| 125 | + ```bash |
| 126 | + npm test |
| 127 | + ``` |
| 128 | + |
| 129 | +4. Create a changeset if your change affects published packages: |
| 130 | + |
| 131 | + Create a new file in `.changeset/` directory (e.g., `.changeset/your-feature-name.md`): |
| 132 | + ```markdown |
| 133 | + --- |
| 134 | + "@cloudflare/sandbox": patch |
| 135 | + --- |
| 136 | + |
| 137 | + Brief description of your change |
| 138 | + ``` |
| 139 | + |
| 140 | + Use `patch` for bug fixes, `minor` for new features, `major` for breaking changes. |
| 141 | + |
| 142 | +5. Push your branch and create a PR |
| 143 | + |
| 144 | +6. **CI runs automatically:** |
| 145 | + - **Unit tests** for `@cloudflare/sandbox` and `@repo/sandbox-container` |
| 146 | + - **E2E tests** that deploy a real test worker to Cloudflare and run integration tests |
| 147 | + - Both test suites MUST pass |
| 148 | + |
| 149 | +7. After approval and passing tests, merge to main |
| 150 | + |
| 151 | +8. **Automated release** (no manual intervention): |
| 152 | + - Changesets action creates a "Version Packages" PR when changesets exist |
| 153 | + - Merging that PR triggers automated npm + Docker Hub publishing |
| 154 | + - Beta releases published on every main commit |
| 155 | + - Stable releases published when changesets are merged |
| 156 | + |
| 157 | +## Testing Architecture |
| 158 | + |
| 159 | +**Tests are critical** - they verify functionality at multiple levels and run on every PR. |
| 160 | + |
| 161 | +**Development practice:** After making any meaningful code change: |
| 162 | +1. Run `npm run check` to catch type errors (these often expose real issues) |
| 163 | +2. Run `npm test` to verify unit tests pass |
| 164 | +3. Run E2E tests if touching core functionality |
| 165 | + |
| 166 | +### Unit Tests |
| 167 | + |
| 168 | +Run these frequently during development: |
| 169 | + |
| 170 | +```bash |
| 171 | +# All unit tests |
| 172 | +npm test |
| 173 | + |
| 174 | +# Specific package |
| 175 | +npm test -w @cloudflare/sandbox # SDK tests (Workers runtime) |
| 176 | +npm test -w @repo/sandbox-container # Container runtime tests (Bun) |
| 177 | +``` |
| 178 | + |
| 179 | +**Architecture:** |
| 180 | +- **SDK tests** (`packages/sandbox/tests/`) run in Workers runtime via `@cloudflare/vitest-pool-workers` |
| 181 | +- **Container tests** (`packages/sandbox-container/tests/`) run in Bun runtime |
| 182 | +- Mock container for isolated testing (SDK), no Docker required |
| 183 | +- Fast feedback loop for development |
| 184 | + |
| 185 | +**Known issue:** Sandbox unit tests may hang on exit due to vitest-pool-workers workerd shutdown issue. This is cosmetic - tests still pass/fail correctly. |
| 186 | + |
| 187 | +### E2E Tests |
| 188 | + |
| 189 | +Run before creating PRs to verify end-to-end functionality: |
| 190 | + |
| 191 | +```bash |
| 192 | +# All E2E tests (requires Docker) |
| 193 | +npm run test:e2e |
| 194 | + |
| 195 | +# Single test file |
| 196 | +npm run test:e2e -- -- tests/e2e/process-lifecycle-workflow.test.ts |
| 197 | + |
| 198 | +# Single test within a file |
| 199 | +npm run test:e2e -- -- tests/e2e/git-clone-workflow.test.ts -t 'should handle cloning to default directory' |
| 200 | +``` |
| 201 | + |
| 202 | +**Architecture:** |
| 203 | +- Tests in `tests/e2e/` run against real Cloudflare Workers + Docker containers |
| 204 | +- **In CI**: Tests deploy to actual Cloudflare infrastructure and run against deployed workers |
| 205 | +- **Locally**: Each test file spawns its own `wrangler dev` instance |
| 206 | +- Config: `vitest.e2e.config.ts` (root level) |
| 207 | +- Sequential execution (`singleFork: true`) to prevent container resource contention |
| 208 | +- Longer timeouts (2min per test) for container operations |
| 209 | + |
| 210 | +**CI behavior:** E2E tests in CI (`pullrequest.yml`): |
| 211 | +1. Build Docker image locally (`npm run docker:local`) |
| 212 | +2. Deploy test worker to Cloudflare with unique name (pr-XXX) |
| 213 | +3. Run E2E tests against deployed worker URL |
| 214 | +4. Cleanup test deployment after tests complete |
| 215 | + |
| 216 | +## Client Architecture Pattern |
| 217 | + |
| 218 | +The SDK uses a modular client pattern in `packages/sandbox/src/clients/`: |
| 219 | + |
| 220 | +- **BaseClient**: Abstract HTTP client with request/response handling |
| 221 | +- **SandboxClient**: Aggregates all specialized clients |
| 222 | +- **Specialized clients**: CommandClient, FileClient, ProcessClient, PortClient, GitClient, UtilityClient, InterpreterClient |
| 223 | + |
| 224 | +Each client handles a specific domain and makes HTTP requests to the container's API. |
| 225 | + |
| 226 | +## Container Runtime Architecture |
| 227 | + |
| 228 | +The container runtime (`packages/sandbox-container/src/`) uses: |
| 229 | + |
| 230 | +- **Dependency Injection**: `core/container.ts` manages service lifecycle |
| 231 | +- **Router**: Simple HTTP router with middleware support |
| 232 | +- **Handlers**: Route handlers in `handlers/` directory |
| 233 | +- **Services**: Business logic in `services/` (CommandService, FileService, ProcessService, etc.) |
| 234 | +- **Managers**: Stateful managers in `managers/` (ProcessManager, PortManager) |
| 235 | + |
| 236 | +Entry point: `packages/sandbox-container/src/index.ts` starts Bun HTTP server on port 3000. |
| 237 | + |
| 238 | +## Monorepo Structure |
| 239 | + |
| 240 | +Uses npm workspaces + Turbo: |
| 241 | +- `packages/sandbox`: Main SDK package |
| 242 | +- `packages/shared`: Shared types |
| 243 | +- `packages/sandbox-container`: Container runtime |
| 244 | +- `examples/`: Working example projects |
| 245 | +- `tooling/`: Shared TypeScript configs |
| 246 | + |
| 247 | +Turbo handles task orchestration (`turbo.json`) with dependency-aware builds. |
| 248 | + |
| 249 | +## Coding Standards |
| 250 | + |
| 251 | +### TypeScript |
| 252 | + |
| 253 | +**Never use the `any` type** unless absolutely necessary (which should be a final resort): |
| 254 | +- First, look for existing types that can be reused appropriately |
| 255 | +- If no suitable type exists, define a proper type in the right location: |
| 256 | + - Shared types → `packages/shared/src/types.ts` or relevant subdirectory |
| 257 | + - SDK-specific types → `packages/sandbox/src/clients/types.ts` or appropriate client file |
| 258 | + - Container-specific types → `packages/sandbox-container/src/` with appropriate naming |
| 259 | +- Use the newly defined type everywhere appropriate for consistency |
| 260 | +- This ensures type safety and catches errors at compile time rather than runtime |
| 261 | + |
| 262 | +### Git Commits |
| 263 | + |
| 264 | +**Follow the 7 rules for great commit messages** (from https://cbea.ms/git-commit/): |
| 265 | + |
| 266 | +1. **Separate subject from body with a blank line** |
| 267 | +2. **Limit the subject line to 50 characters** |
| 268 | +3. **Capitalize the subject line** |
| 269 | +4. **Do not end the subject line with a period** |
| 270 | +5. **Use the imperative mood in the subject line** (e.g., "Add feature" not "Added feature") |
| 271 | +6. **Wrap the body at 72 characters** |
| 272 | +7. **Use the body to explain what and why vs. how** |
| 273 | + |
| 274 | +**Be concise, not verbose.** Every word should add value. Avoid unnecessary details about implementation mechanics - focus on what changed and why it matters. |
| 275 | + |
| 276 | +Example: |
| 277 | +``` |
| 278 | +Add session isolation for concurrent executions |
| 279 | +
|
| 280 | +Previously, multiple concurrent exec() calls would interfere with each |
| 281 | +other's working directories and environment variables. This adds proper |
| 282 | +session management to isolate execution contexts. |
| 283 | +
|
| 284 | +The SessionManager tracks active sessions and ensures cleanup when |
| 285 | +processes complete. This is critical for multi-tenant scenarios where |
| 286 | +different users share the same sandbox instance. |
| 287 | +``` |
| 288 | + |
| 289 | +## Important Patterns |
| 290 | + |
| 291 | +### Error Handling |
| 292 | +- Custom error classes in `packages/shared/src/errors/` |
| 293 | +- Errors flow from container → Sandbox DO → Worker |
| 294 | +- Use `ErrorCode` enum for consistent error types |
| 295 | + |
| 296 | +### Logging |
| 297 | +- Centralized logger from `@repo/shared` |
| 298 | +- Structured logging with component context |
| 299 | +- Configurable via `SANDBOX_LOG_LEVEL` and `SANDBOX_LOG_FORMAT` env vars |
| 300 | + |
| 301 | +### Session Management |
| 302 | +- Sessions isolate execution contexts (working directory, env vars, etc.) |
| 303 | +- Default session created automatically |
| 304 | +- Multiple sessions per sandbox supported |
| 305 | + |
| 306 | +### Port Management |
| 307 | +- Expose internal services via preview URLs |
| 308 | +- Token-based authentication for exposed ports |
| 309 | +- Automatic cleanup on sandbox sleep |
| 310 | +- **Production requirement**: Preview URLs require custom domain with wildcard DNS (*.yourdomain.com) |
| 311 | + - `.workers.dev` domains do NOT support the subdomain patterns needed for preview URLs |
| 312 | + - See Cloudflare docs for "Deploy to Production" guide when ready to expose services |
| 313 | + |
| 314 | +## Version Management & Releases |
| 315 | + |
| 316 | +**Releases are fully automated** via GitHub Actions (`.github/workflows/release.yml`) and changesets (`.changeset/`): |
| 317 | + |
| 318 | +- **Changesets**: Create a `.changeset/your-feature-name.md` file to document changes affecting published packages (see PR process above) |
| 319 | +- **Beta releases**: Published automatically on every push to main (`@beta` tag on npm) |
| 320 | +- **Stable releases**: When changesets exist, the "Version Packages" PR is auto-created. Merging it triggers: |
| 321 | + 1. Version bump in `package.json` |
| 322 | + 2. Docker image build and push to Docker Hub (multi-arch: amd64, arm64) |
| 323 | + 3. npm package publish with updated version |
| 324 | +- **Version synchronization**: Docker image version always matches npm package version (enforced via `ARG SANDBOX_VERSION` in Dockerfile) |
| 325 | + |
| 326 | +**SDK version tracked in**: `packages/sandbox/src/version.ts` |
| 327 | + |
| 328 | +## Container Base Image |
| 329 | + |
| 330 | +The container runtime uses Ubuntu 22.04 with: |
| 331 | +- Python 3.11 (with matplotlib, numpy, pandas, ipython) |
| 332 | +- Node.js 20 LTS |
| 333 | +- Bun 1.x runtime (powers the container HTTP server) |
| 334 | +- Git, curl, wget, jq, and other common utilities |
| 335 | + |
| 336 | +When modifying the base image (`packages/sandbox/Dockerfile`), remember: |
| 337 | +- Keep images lean - every MB affects cold start time |
| 338 | +- Pin versions for reproducibility |
| 339 | +- Clean up package manager caches to reduce image size |
0 commit comments