Appearance
Claude Code
claude is a pre-built AI Agents runtime environment that includes the Claude Code CLI and official MCP servers. It allows you to run Anthropic's intelligent coding agent in a headless, isolated sandbox with full access to the filesystem, terminal, and git.
The template comes pre-installed with Node.js, git, ripgrep, vim, GitHub CLI, and frontend scaffolding tools like pnpm/tsx/vite. No API keys are baked into the image; authentication is provided via envs at sandbox creation or through secret injection rules.
Template Contents
| Component | Description |
|---|---|
claude | Claude Code CLI |
| MCP servers | @modelcontextprotocol/server-filesystem / server-github / server-memory / server-sequential-thinking |
/home/user/.claude.json | Pre-set onboarding state to avoid blocking on the official service connectivity check during first interaction |
| Base tools | Node.js 24.x, git, ripgrep, vim, GitHub CLI, pnpm/tsx/vite, etc. |
Creating a Sandbox
Pass ANTHROPIC_API_KEY via envs:
javascript
import { Sandbox } from 'e2b'
const sandbox = await Sandbox.create('claude', {
envs: { ANTHROPIC_API_KEY: process.env.ANTHROPIC_API_KEY },
})To point to an Anthropic-compatible gateway, additionally inject ANTHROPIC_BASE_URL. Replace the example host with your actual gateway host, and make sure it matches the base_url configured in your injection rule:
javascript
const sandbox = await Sandbox.create('claude', {
envs: {
ANTHROPIC_API_KEY: process.env.ANTHROPIC_API_KEY,
ANTHROPIC_BASE_URL: 'https://api.example.com',
},
})Common CLI Flags
| Flag | Description |
|---|---|
-p "<prompt>" | Headless (non-interactive) mode, passes the prompt directly to the CLI |
--dangerously-skip-permissions | Skips tool call approval; safe to use in an isolated sandbox environment |
--output-format json | Returns a single JSON response |
--output-format stream-json | Outputs a real-time JSONL event stream |
--resume <sessionId> | Resumes a previous session |
--system-prompt <text> | Injects a task-level system prompt |
Headless Execution
javascript
import { Sandbox } from 'e2b'
const sandbox = await Sandbox.create('claude', {
envs: { ANTHROPIC_API_KEY: process.env.ANTHROPIC_API_KEY },
})
const result = await sandbox.commands.run(
`claude --dangerously-skip-permissions -p "Create a hello world HTTP server in Go"`,
)
console.log(result.stdout)
await sandbox.kill()Git Repository Integration
Clone a repository inside the sandbox and let Claude Code make changes and show the diff:
javascript
import { Sandbox } from 'e2b'
const sandbox = await Sandbox.create('claude', {
envs: { ANTHROPIC_API_KEY: process.env.ANTHROPIC_API_KEY },
timeoutMs: 600_000,
})
await sandbox.git.clone('https://github.com/your-org/your-repo.git', {
path: '/home/user/repo',
username: 'x-access-token',
password: process.env.GITHUB_TOKEN,
depth: 1,
})
const result = await sandbox.commands.run(
`cd /home/user/repo && claude --dangerously-skip-permissions -p "Add error handling to all API endpoints"`,
{ onStdout: (data) => process.stdout.write(data) },
)
const diff = await sandbox.commands.run('cd /home/user/repo && git diff')
console.log(diff.stdout)
await sandbox.kill()Structured JSON Output
javascript
const result = await sandbox.commands.run(
`claude --dangerously-skip-permissions --output-format json -p "Review this codebase and list all security issues as JSON"`,
)
const response = JSON.parse(result.stdout)
console.log(response)Streaming Event Handling
javascript
const result = await sandbox.commands.run(
`cd /home/user/repo && claude --dangerously-skip-permissions --output-format stream-json -p "Find and fix all TODO comments"`,
{
onStdout: (data) => {
for (const line of data.split('\n').filter(Boolean)) {
const event = JSON.parse(line)
if (event.type === 'assistant') {
console.log(`[assistant] tokens: ${event.message.usage?.output_tokens}`)
} else if (event.type === 'result') {
console.log(`[done] ${event.subtype} in ${event.duration_ms}ms`)
}
}
},
},
)Session Resumption
The session_id returned by --output-format json can be used with --resume to split multi-step tasks across multiple calls:
javascript
const initial = await sandbox.commands.run(
`cd /home/user/repo && claude --dangerously-skip-permissions --output-format json -p "Analyze the codebase and create a refactoring plan"`,
)
const sessionId = JSON.parse(initial.stdout).session_id
const followUp = await sandbox.commands.run(
`cd /home/user/repo && claude --dangerously-skip-permissions --resume ${sessionId} -p "Now implement step 1 of the plan"`,
{ onStdout: (data) => process.stdout.write(data) },
)Custom System Prompt (CLAUDE.md)
Write project-level conventions into CLAUDE.md, and Claude Code will automatically read it:
javascript
await sandbox.files.write('/home/user/repo/CLAUDE.md', `
You are working on a Go microservice.
Always use structured logging with slog.
Follow the project's error handling conventions in pkg/errors.
`)
const result = await sandbox.commands.run(
`cd /home/user/repo && claude --dangerously-skip-permissions -p "Add a /healthz endpoint"`,
)Using Secret Injection
When injecting ANTHROPIC_API_KEY directly via envs, the real key ends up in the sandbox process's environment variables. If you want the real key to stay entirely on the platform side and be unreadable by code inside the sandbox, use secret injection. The CLI still sends requests to api.anthropic.com (or a custom ANTHROPIC_BASE_URL), and the platform rewrites the x-api-key header to the real key before forwarding it upstream.
How It Works
text
Sandbox (claude CLI) ──► Platform MITM ──► api.anthropic.com / Compatible Gateway
x-api-key=placeholder Injects real keyFor the Claude Code CLI to hit the injection rule, two conditions must be met:
ANTHROPIC_BASE_URLmust match the rule'sbase_url(host part): The platform matches by SNI hostname. If the CLI's defaultapi.anthropic.comdoesn't match the rule, the request won't be intercepted and will go directly to Anthropic.ANTHROPIC_API_KEYmust be set to any non-empty placeholder: The CLI needs a key field to initiate a request during its local pre-check. The real key is written into the header by the platform on egress; the local placeholder value is never sent out.
Method 1: Reference a Saved Injection Rule
First, create an anthropic type rule in the Sufy Console or through the Open API. For an Anthropic-compatible gateway, set base_url to the gateway host when creating the rule.
When creating the sandbox, reference the <rule-id> and set the Claude Code CLI's ANTHROPIC_API_KEY to any placeholder:
javascript
import { Sandbox } from 'e2b'
const sandbox = await Sandbox.create('claude', {
envs: {
ANTHROPIC_API_KEY: 'placeholder',
// Only needed if the rule specifies a base_url
ANTHROPIC_BASE_URL: 'https://api.example.com',
},
injections: [{ id: '<rule-id>' }],
})
const result = await sandbox.commands.run(
`claude --dangerously-skip-permissions -p "Create a hello world HTTP server in Go"`,
)
console.log(result.stdout)Method 2: Inline Injection (No Pre-created Rule Needed)
Suitable for temporary verification; the rule is not retained after the sandbox is destroyed. Pass the inline rule when creating the sandbox through the Open API:
bash
curl -X POST "$SUFY_SANDBOX_API_URL/sandboxes" \
-H "X-API-Key: $SUFY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"templateID": "claude",
"envVars": {
"ANTHROPIC_API_KEY": "placeholder"
},
"injections": [
{
"type": "anthropic",
"api_key": "sk-ant-real-xxx"
}
]
}'If using a compatible gateway, ensure base_url, ANTHROPIC_BASE_URL, and the rule are consistent:
bash
curl -X POST "$SUFY_SANDBOX_API_URL/sandboxes" \
-H "X-API-Key: $SUFY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"templateID": "claude",
"envVars": {
"ANTHROPIC_API_KEY": "placeholder",
"ANTHROPIC_BASE_URL": "https://api.example.com"
},
"injections": [
{
"type": "anthropic",
"api_key": "sk-real-xxx",
"base_url": "https://api.example.com"
}
]
}'Important Notes
- First matching rule for the same host takes effect: If multiple rules have the same
base_urlhostname, the platform applies only the first one in list order. Within a single sandbox, the CLI can only point to oneANTHROPIC_BASE_URL, so only the rule corresponding to that host will be triggered. - Placeholder cannot be empty:
ANTHROPIC_API_KEY=""will cause the CLI to error out locally. Any non-empty string (e.g.,placeholder) works. --dangerously-skip-permissionsdoes not affect injection: Injection happens at the network egress stage and is independent of whether the CLI skips tool approval.- Use persistent rules for production: Inline injection is convenient for temporary debugging, but saved rules are easier to audit, rotate, and revoke for team collaboration or long-term use.
References
- Claude Code CLI documentation: https://docs.claude.com/claude-code