# 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: ```bash no-run printf %s "$ANTHROPIC_API_KEY" | npx withruntime secrets set LLM_API_KEY --host api.anthropic.com ``` Every 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](/docs/security#secrets-sandboxes-never-see)). `LLM_MODEL` is not sensitive, so it goes in the command's environment. ## Run a task headless ```ts check import { 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")); ``` ```python check import sys from withruntime import Sandbox repo = "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.12`** is the install OpenHands recommends. uv ships in the sandbox image and finds its Python 3.12, and the `openhands` command lands in `/workspace/.local/bin`, first on `PATH`. - **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 ""` works for short ones. - **`--headless`** runs with no UI and approves every action; it needs `-t` or `-f`. **`--json`** streams the run's events as JSON Lines. - **`--override-with-envs`** makes the CLI read `LLM_MODEL` and the placeholder in `LLM_API_KEY`. The model string is in LiteLLM's `provider/model` form. - **The working directory** is the checkout. The CLI works where it is started, or in `OPENHANDS_WORK_DIR` when 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](/how-to/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 ""` | Task text | | `-f`, `--file ` | Task from a file | | `--json` | JSONL events, with `--headless` only | | `--override-with-envs` | Read `LLM_MODEL`, `LLM_API_KEY`, `LLM_BASE_URL` | | `--resume `, `--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](/how-to/pause-and-resume-a-sandbox) 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 `--`: ```bash no-run openhands mcp add runtime --transport stdio npx -- -y withruntime mcp ``` The same entry by hand, in `~/.openhands/mcp.json`: ```json no-run { "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](/docs/mcp#add-it-to-your-agent)). 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](/docs/pricing)). The LLM provider bills tokens on your key. The free trial covers 50 sandbox hours, no card: ```bash no-run npx withruntime sandbox run --trial -- python3 --version ``` To score many headless runs side by side, see [agent evals and SWE-bench](/use-cases/agent-evals-and-swe-bench). Similar setups: [Aider](/integrations/aider) and [Goose](/integrations/goose). ## Sources Checked 25 September 2026. - [Installation](https://docs.openhands.dev/openhands/usage/cli/installation): `uv tool install openhands --python 3.12`, `~/.openhands/settings.json` - [Headless mode](https://docs.openhands.dev/openhands/usage/cli/headless): `--headless`, `-t`, `-f`, `--json`, always-approve - [MCP servers](https://docs.openhands.dev/openhands/usage/cli/mcp-servers): `openhands mcp add` and `~/.openhands/mcp.json` - [OpenHands CLI source](https://github.com/OpenHands/OpenHands-CLI): `--override-with-envs`, `LLM_MODEL`, `LLM_API_KEY`, `LLM_BASE_URL`, `OPENHANDS_WORK_DIR` - [openhands on PyPI](https://pypi.org/project/openhands/): version 1.16.0, Python 3.12 Facts on this page were checked on 25 September 2026.