# Claude tool use: how to run the code Claude writes in a sandbox Give Claude a `run_command` tool, run each `tool_use` block in a microVM, and reply with a `tool_result` holding the output and `is_error`. **A Runtime sandbox gives Claude a whole Linux machine for the conversation, with the internet off and billed on the CPU it uses.** Every sandbox is a Firecracker microVM with its own kernel, running Ubuntu 24.04 with Python 3.12, Node.js 24, git and gcc. Waiting between Claude's turns, a 2 vCPU, 4 GiB sandbox costs $0.03125 an hour ([pricing](/docs/pricing)). The code uses `claude-opus-5-5`, the model Anthropic's overview told developers to start with on 25 September 2026. ## One sandbox for the whole conversation Claude often works in steps: write a file, run it, read the error, fix it. So this loop creates one sandbox before the first request and keeps it until Claude stops asking for the tool. Files from one call are there for the next. ```ts check import Anthropic from "@anthropic-ai/sdk"; import { Sandbox } from "withruntime"; const anthropic = new Anthropic(); // ANTHROPIC_API_KEY stays in this process const tools: Anthropic.Tool[] = [ { name: "run_command", description: "Run one bash command in a private Ubuntu 24.04 sandbox that has Python 3.12, " + "Node.js 24 and gcc but no internet. The working directory is /workspace and " + "files persist between calls in this conversation. Returns the exit code, " + "stdout and stderr; each command is stopped after 60 seconds.", input_schema: { type: "object", properties: { command: { type: "string", description: "A bash command line" } }, required: ["command"], }, }, ]; await using sbx = await Sandbox.create({ network: { internet: false }, timeoutSeconds: 1800, onLeaseEnd: "stop", }); const messages: Anthropic.MessageParam[] = [ { role: "user", content: "Write a prime sieve in C, compile it and print the primes below 100." }, ]; for (let turn = 0; turn < 10; turn++) { const reply = await anthropic.messages.create({ model: "claude-opus-5-5", max_tokens: 4096, tools, messages, }); messages.push({ role: "assistant", content: reply.content }); if (reply.stop_reason !== "tool_use") { for (const block of reply.content) if (block.type === "text") console.log(block.text); break; } const results: Anthropic.ToolResultBlockParam[] = []; for (const block of reply.content) { if (block.type !== "tool_use") continue; const { command } = block.input as { command: string }; const run = await sbx.exec(["bash", "-c", command], { timeoutMs: 60_000 }); results.push({ type: "tool_result", tool_use_id: block.id, content: JSON.stringify({ exit_code: run.exitCode, timed_out: run.timedOut, stdout: run.stdout, stderr: run.stderr, }), is_error: run.exitCode !== 0, }); } messages.push({ role: "user", content: results }); } ``` ```python check import json import anthropic from withruntime import Sandbox client = anthropic.Anthropic() # ANTHROPIC_API_KEY stays in this process tools = [ { "name": "run_command", "description": "Run one bash command in a private Ubuntu 24.04 sandbox that has Python 3.12, " "Node.js 24 and gcc but no internet. The working directory is /workspace and " "files persist between calls in this conversation. Returns the exit code, " "stdout and stderr; each command is stopped after 60 seconds.", "input_schema": { "type": "object", "properties": {"command": {"type": "string", "description": "A bash command line"}}, "required": ["command"], }, } ] with Sandbox.create(network={"internet": False}, timeout_seconds=1800, on_lease_end="stop") as sbx: messages = [{"role": "user", "content": "Write a prime sieve in C, compile it and print the primes below 100."}] for _ in range(10): reply = client.messages.create(model="claude-opus-5-5", max_tokens=4096, tools=tools, messages=messages) messages.append({"role": "assistant", "content": reply.content}) if reply.stop_reason != "tool_use": print("".join(block.text for block in reply.content if block.type == "text")) break results = [] for block in reply.content: if block.type != "tool_use": continue run = sbx.exec(["bash", "-c", block.input["command"]], timeout_ms=60_000) output = {"exit_code": run.exit_code, "timed_out": run.timed_out, "stdout": run.stdout, "stderr": run.stderr} results.append( {"type": "tool_result", "tool_use_id": block.id, "content": json.dumps(output), "is_error": run.exit_code != 0} ) messages.append({"role": "user", "content": results}) ``` The command is Claude's to write, so it runs under `bash -c` inside the sandbox, where a shell is exactly what Claude asked for. Your own process never hands the text to a shell. The ten-turn cap bounds the loop, and leaving the block stops the sandbox. ## The rules Anthropic sets for tool results Anthropic's guide on handling tool calls, read 25 September 2026, is strict about shape. A malformed reply is a 400 error, not a wrong answer. | Rule | How the loop meets it | | ---------------------------------------------------------------- | ------------------------------------------------------ | | `stop_reason` is `tool_use` when Claude wants a client tool | The loop runs tools only then | | Each result names the `id` of its `tool_use` block | `tool_use_id: block.id` | | Results come in the very next `user` message, first in `content` | Every result goes in one `user` message, nothing else | | `is_error: true` marks a failed execution | Set from a non-zero exit code | | Results can carry text the model should not obey | Output stays inside `tool_result`, never in the prompt | Claude may call the tool more than once in a turn; every `tool_use` block gets its own result. With `is_error` set, Claude reads the traceback and tries again, which is the point of returning `stderr` whole. ## Anthropic's code execution tool, or this? Anthropic runs its own server tool, which "allows Claude to run Bash commands and manipulate files" in a container Anthropic hosts. Its page, checked 25 September 2026, gives the container 1 CPU, 5 GiB of memory, and internet "completely disabled for security", so Claude "can't download or install additional packages at runtime". Past 1,550 free hours a month per organization it costs $0.05 an hour per container, with a 5-minute minimum. A Runtime sandbox differs where a build or an analysis gets stuck: - **Packages on your terms.** Allow PyPI or npm for an install, then turn the internet off before Claude's code runs; rules apply at once and bind root ([the network](/docs/sandbox-environment#the-network)). - **Size you choose.** 2 vCPUs and 4 GiB by default, and `vcpu`, memory and disk are set per sandbox. - **State that outlives a request.** A paused sandbox keeps its files, memory and processes for 1 to 365 days. - **Any model.** The same tool serves Claude and every other model you call. Costs for short and long runs side by side are in [Claude code execution tool alternative](/compare/claude-code-execution-tool-alternative). ## Where the Anthropic key goes Nowhere near the sandbox. The loop calls the Messages API from your server, and only Claude's commands run inside, so `ANTHROPIC_API_KEY` stays in your process. When Claude Code or another agent runs inside the sandbox and calls Anthropic itself, store the key as a Runtime secret bound to `api.anthropic.com`; the sandbox then holds only a placeholder that is worthless anywhere else ([secrets](/docs/security#secrets-sandboxes-never-see)). [Claude Code in a sandbox](/integrations/claude-code) walks through that case. ## Using the Claude Agent SDK The Agent SDK runs its own loop. `runtimeMcpServer(sbx)` from `withruntime/claude-agent-sdk` hands it Runtime's command and file tools bound to one sandbox ([Claude Agent SDK](/docs/frameworks#claude-agent-sdk)). ## Start on the free trial New accounts get 50 free sandbox hours, no card. The first command prints a link to approve in your browser: ```bash no-run npx withruntime sandbox run --trial -- gcc --version ``` More: [run untrusted LLM code](/use-cases/run-untrusted-llm-code), [coding agent sandbox](/use-cases/coding-agent-sandbox), [egress control](/glossary/egress-control). ## Sources Checked 25 September 2026. - [Anthropic: define tools](https://platform.claude.com/docs/en/agents-and-tools/tool-use/define-tools) - [Anthropic: handle tool calls](https://platform.claude.com/docs/en/agents-and-tools/tool-use/handle-tool-calls) - [Anthropic: models overview](https://platform.claude.com/docs/en/about-claude/models/overview) - [Anthropic: code execution tool](https://platform.claude.com/docs/en/agents-and-tools/tool-use/code-execution-tool) - [@anthropic-ai/sdk on npm](https://www.npmjs.com/package/@anthropic-ai/sdk) (0.128.0) and [anthropic on PyPI](https://pypi.org/project/anthropic/) (1.8.0) Facts on this page were checked on 25 September 2026.