How to run OpenHands in a cloud sandbox
Install the OpenHands CLI with uv tool install openhands in a Linux microVM, pass the model by environment and run openhands --headless.
Runtime is the isolated machine OpenHands' headless mode assumes, and the
LLM key never lands on it. The OpenHands docs state that headless mode always
runs in always-approve mode: every action the agent chooses executes with no
confirmation. On Runtime each run gets a Firecracker microVM with its own
kernel, Ubuntu 24.04 and Python 3.12, the exact Python version the openhands
package requires. The key is a Runtime secret, so LLM_API_KEY inside the
sandbox is only a placeholder. A 2 vCPU, 4 GiB sandbox costs $0.03125 an hour
while the agent waits on the model. The CLI was at version 1.16.0 on PyPI on
25 September 2026.
| Question | Answer on Runtime |
|---|---|
| Where do OpenHands' commands run? | In the sandbox's own Linux, not on your runner |
| Where does the LLM key live? | On Runtime's servers; the sandbox holds a placeholder |
| What can the agent reach? | The hosts you allow, such as the model API and PyPI |
| Can OpenHands on a laptop use sandboxes? | Yes, through Runtime's MCP server |
Store the key under OpenHands' variable
The CLI reads its model settings from three variables when you pass
--override-with-envs: LLM_MODEL, LLM_API_KEY and, optionally,
LLM_BASE_URL. Without the flag it ignores them and uses
~/.openhands/settings.json. Store the key under LLM_API_KEY, tied to the
provider's host. For Anthropic:
Terminalprintf %s "$ANTHROPIC_API_KEY" | npx withruntime secrets set LLM_API_KEY --host api.anthropic.comEvery sandbox then has LLM_API_KEY set to a placeholder. The proxy replaces
it with the real key in HTTPS requests to api.anthropic.com and leaves it
worthless everywhere else
(secrets sandboxes never see).
LLM_MODEL is not sensitive, so it goes in the command's environment.
Run a task headless
TypeScriptimport { writeFile } from "node:fs/promises";import { Sandbox } from "withruntime";const repo = "https://github.com/your-org/your-repo";const task = "Issue #212: the retry decorator ignores max_attempts. Reproduce it with a test, then fix it.";await using sbx = await Sandbox.create({ diskMiB: 10240, timeoutSeconds: 3600 });const setup = { check: true, timeoutMs: 900_000 } as const;await sbx.exec("uv tool install openhands --python 3.12", setup);await sbx.exec(["git", "clone", "--depth", "1", repo, "/workspace/app"], setup);await sbx.files.write("/workspace/task.md", task);await sbx.network.set({ internet: true, allow: ["api.anthropic.com", "pypi.org", "files.pythonhosted.org"],});const run = await sbx.exec( ["openhands", "--headless", "--override-with-envs", "--json", "-f", "/workspace/task.md"], { cwd: "/workspace/app", env: { LLM_MODEL: "anthropic/claude-sonnet-4-5-20250929" }, timeoutMs: 2_400_000, onStderr: (text) => process.stderr.write(text), },);const events = run.stdout.split("\n").filter((line) => line.startsWith("{"));console.log(run.exitCode, `${events.length} events`);await sbx.exec("git add -A && git diff --cached > /workspace/openhands.patch", { cwd: "/workspace/app", check: true,});await writeFile("openhands.patch", await sbx.files.readText("/workspace/openhands.patch"));Pythonimport sysfrom withruntime import Sandboxrepo = "https://github.com/your-org/your-repo"task = "Issue #212: the retry decorator ignores max_attempts. Reproduce it with a test, then fix it."with Sandbox.create(disk_mib=10240, timeout_seconds=3600) as sbx: sbx.exec("uv tool install openhands --python 3.12", check=True, timeout_ms=900_000) sbx.exec(["git", "clone", "--depth", "1", repo, "/workspace/app"], check=True, timeout_ms=900_000) sbx.files.write("/workspace/task.md", task) sbx.network.set(internet=True, allow=["api.anthropic.com", "pypi.org", "files.pythonhosted.org"]) run = sbx.exec( ["openhands", "--headless", "--override-with-envs", "--json", "-f", "/workspace/task.md"], cwd="/workspace/app", env={"LLM_MODEL": "anthropic/claude-sonnet-4-5-20250929"}, timeout_ms=2_400_000, on_stderr=sys.stderr.write, ) events = [line for line in run.stdout.splitlines() if line.startswith("{")] print(run.exit_code, len(events), "events") sbx.exec("git add -A && git diff --cached > /workspace/openhands.patch", cwd="/workspace/app", check=True) with open("openhands.patch", "w") as file: file.write(sbx.files.read_text("/workspace/openhands.patch"))Reading the code:
uv tool install openhands --python 3.12is the install OpenHands recommends. uv ships in the sandbox image and finds its Python 3.12, and theopenhandscommand lands in/workspace/.local/bin, first onPATH.- A bigger disk. The package brings its SDK and tools with it; 10 GiB leaves room for them and for the project's own dependencies.
- The task goes in a file and reaches the CLI through
-f, so a long issue description with code blocks arrives intact.-t "<text>"works for short ones. --headlessruns with no UI and approves every action; it needs-tor-f.--jsonstreams the run's events as JSON Lines.--override-with-envsmakes the CLI readLLM_MODELand the placeholder inLLM_API_KEY. The model string is in LiteLLM'sprovider/modelform.- The working directory is the checkout. The CLI works where it is
started, or in
OPENHANDS_WORK_DIRwhen that is set. - The patch comes back as a file, so a large change is never cut at the 64 KiB result limit.
PyPI stays on the allow list because the agent usually installs the project's dependencies to run its tests. Remove it for a project whose dependencies are already installed, and the agent can reach the model and nothing else (turn off sandbox internet).
CLI flags for automation
From the OpenHands CLI docs and its --help, 25 September 2026:
| Flag | Meaning |
|---|---|
--headless |
No UI, every action approved; needs -t or -f |
-t, --task "<text>" |
Task text |
-f, --file <path> |
Task from a file |
--json |
JSONL events, with --headless only |
--override-with-envs |
Read LLM_MODEL, LLM_API_KEY, LLM_BASE_URL |
--resume <id>, --last |
Carry on an earlier conversation |
--always-approve, --llm-approve |
Interactive approval modes, for runs outside headless |
Conversations are saved under ~/.openhands/conversations in the sandbox, so --resume
works on a kept sandbox. Pause it between
tasks: the checkout, the history and any running services stay, and compute
billing stops.
Point OpenHands at Runtime's tools
When OpenHands runs on your own machine, Runtime's MCP server gives it
sandboxes to run code in. The CLI's mcp add takes the command, then its
arguments after --:
Terminalopenhands mcp add runtime --transport stdio npx -- -y withruntime mcpThe same entry by hand, in ~/.openhands/mcp.json:
JSON{ "mcpServers": { "runtime": { "command": "npx", "args": ["-y", "withruntime", "mcp"] } }}The first Runtime call answers with a link and a code. Approve Connect
agent in the browser and every tool appears; no key goes in the file
(MCP). Scripts and CI set RUNTIME_API_KEY
from a secret manager instead.
| Tool | What OpenHands can do |
|---|---|
runtime_sandbox_create |
Start a microVM for a risky command or a test run |
runtime_sandbox_exec |
Run it and read exit code, stdout and stderr |
runtime_sandbox_process |
Follow a long job started in the background |
runtime_volume_create |
Keep a cache disk between sandboxes |
runtime_sandbox_manage |
Stop the sandbox when the job is done |
Cost of a headless run
Runtime charges $0.025 per vCPU-hour for CPU actually used, never less than a twentieth of a vCPU, and $0.0075 per GiB-hour for reserved memory. At 2 vCPUs and 4 GiB that is $0.03125 an hour while OpenHands reasons and $0.08 an hour with both cores building (pricing). The LLM provider bills tokens on your key. The free trial covers 50 sandbox hours, no card:
Terminalnpx withruntime sandbox run --trial -- python3 --versionTo score many headless runs side by side, see agent evals and SWE-bench. Similar setups: Aider and Goose.
Sources
Checked 25 September 2026.
- Installation:
uv tool install openhands --python 3.12,~/.openhands/settings.json - Headless mode:
--headless,-t,-f,--json, always-approve - MCP servers:
openhands mcp addand~/.openhands/mcp.json - OpenHands CLI source:
--override-with-envs,LLM_MODEL,LLM_API_KEY,LLM_BASE_URL,OPENHANDS_WORK_DIR - openhands on PyPI: version 1.16.0, Python 3.12
Facts on this page were checked on 25 September 2026.