# 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 that OpenHands' headless mode assumes, and the LLM key never reaches it.** The OpenHands docs state that headless mode always runs in always-approve mode. Every action the agent chooses runs 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 October 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, the CLI ignores them and reads `~/.openhands/settings.json`. Store the key under `LLM_API_KEY`, bound 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`. Anywhere else, the placeholder is useless ([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 that OpenHands recommends. uv ships in the sandbox image and finds its Python 3.12. The `openhands` command lands in `/workspace/.local/bin`, which is 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.** It reaches the CLI through `-f`, so a long issue description with code blocks arrives intact. `-t ""` works for short tasks. - **`--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 uses 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. For a project whose dependencies are already installed, remove it. The agent can then 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 1.16.0 source, 25 October 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 | The CLI saves conversations under `~/.openhands/conversations` in the sandbox. So `--resume` works on a sandbox you keep. [Pause the sandbox](/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` command 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 returns a link and a code. Approve **Connect agent** in the browser, and every tool appears. The file holds no key ([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_sandboxes_create` | Start a microVM for a risky command or a test run | | `runtime_sandboxes_exec` | Run it and read exit code, stdout and stderr | | `runtime_sandboxes_process` | Follow a long job started in the background | | `runtime_volumes` | Keep a cache disk between sandboxes | | `runtime_sandboxes_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, with a floor of a twentieth of a vCPU. It charges $0.0075 per GiB-hour for reserved memory. At 2 vCPUs and 4 GiB, a run costs $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. Every account has 100 free hours of a 1 GB machine in the first 30 days, with no card: ```bash no-run npx withruntime sandboxes run -- 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 October 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 October 2026.