The line is always the same:
Sandbox failed to start within 120s: 1hCAH2KVIA705fk2ZO3twE
It appears on OpenHands Cloud and on a local install. The wait lives in the app server, not in the cloud scheduler. In the local 1.21 image we ran, the method is wait_for_sandbox_running(timeout=120, poll_interval=2): it checks the sandbox status every two seconds, asks the agent server for /alive, and raises the error after two minutes.
Disclosure: Wallaby Token sells API access to Kimi K3, the model we point OpenHands at.
What has to become ready
Two things. The sandbox has to report RUNNING, and the agent server inside it has to answer a health check. On cloud that sandbox is a remote runtime. Locally it is a Docker container. The same wait covers both, which is why the string is identical.
Cloud failures
Issue #14455, opened and closed on 18 May 2026, is the clean reproduction. The reporter selected a repository on OpenHands Cloud, clicked launch, watched the sandbox load, and got 500: Sandbox failed to start within 120s with a sandbox id. The installation field says OpenHands Cloud.
The opaque message was improved by PR #14885, opened 18 June 2026 and merged 23 July. It reads the runtime's status_detail and appends it to the timeout. Capacity failures, when the scheduler is out of ephemeral storage, CPU, memory, or pods, become a retryable message. Other causes, including ImagePullBackOff, keep the raw detail because a retry will not help.
So the text after the sandbox id is the useful part. If it is missing, you are on an older build. If it says capacity, wait a few minutes. If it says ImagePullBackOff, the image cannot be pulled.
One gap is open. Issue #16874, opened 24 August 2026, reports that a failed sandbox resume is never retried because a one-shot guard is claimed before the request. The failure persists for as long as the conversation page stays mounted.
Local failures before the sandbox
On a local install, the first container is the GUI server. On 1.21, openhands serve runs this:
docker run -it --rm --pull=always \
-e LOG_ALL_EVENTS=true \
-v /var/run/docker.sock:/var/run/docker.sock \
-v ~/.openhands:/.openhands \
-p 3000:3000 \
--add-host host.docker.internal:host-gateway \
--name openhands-app \
docker.openhands.dev/openhands/openhands:latest
The image is 1.79 GB on disk. --pull=always means every start checks the registry first.
Four failures happen before any conversation exists. The first two block the server from starting at all.
If Docker is not running:
❌ Docker daemon is not running.
Please start Docker and try again.
If the shell has no TTY, the -it flag cannot attach:
cannot attach stdin to a TTY-enabled container because stdin is not a terminal
❌ Failed to start OpenHands GUI server.
That second one bites in CI, in agent shells, and in any wrapper that captures output. Run openhands serve from a real terminal, or run the docker run command yourself without -it.
With Docker running and a TTY, the server starts and answers: GET / returned 200 in our test. Two more failures need specific conditions.
If port 3000 is taken, Docker refuses the container:
docker: Error response from daemon: ... Bind for 0.0.0.0:3000 failed: port is already allocated
Check who holds it with lsof -i :3000.
If you mount an existing ~/.openhands from a newer build, the server starts and then returns 500 on settings:
AgentSettings schema_version 6 is newer than supported version 4.
Move ~/.openhands/settings.json aside or run a matching version. Lowering the version field to 4 made the same file load in our test. That is a workaround, not a migration.
The sandbox is a second image
The GUI container does not include the agent runtime. Starting a conversation pulls another image, ghcr.io/openhands/agent-server:1.36.0-python in our test. The runtime image has its own version number, separate from the app's 1.21.
If that pull cannot finish, the start request stays open and the server logs the same line every five seconds. Watch it with docker logs -f openhands-app:
🔄 Downloading Docker Image: ghcr.io/openhands/agent-server:1.36.0-python...
The image is about 1.5 GB compressed and 5.5 GB on disk. On a slow link this pull can consume the whole 120 second window before the sandbox ever reports RUNNING; that is the most likely local way to see the error without inducing it. Pre-pull it on the host and the GUI finds it locally:
docker pull ghcr.io/openhands/agent-server:<tag>
Read the exact tag from the Downloading Docker Image line.
Reproducing the timeout locally
To check that the local path raises the same error, we paused the sandbox container right after it appeared. The status never reached a healthy RUNNING, the wait kept polling, and after two minutes the start task failed:
500: Sandbox failed to start within 120s: oh-agent-server-7ENz1i9Nor6bRSx4Barevx
The task detail carried the sandbox id and no agent server URL. The string is not cloud-only. What differs is what fails to become ready.
When the sandbox does start
With the runtime image cached and the GUI on its default host port, the flow completes. The sandbox container came up in about twelve seconds, loaded 71 skills, created 5 MCP tools, and the conversation reached READY. The agent replied OK, and the conversation title became ✅ Reply with exactly OK. Token usage was 342 prompt tokens, 181 completion tokens, and 256 cache reads.
The first attempt failed because of the host port. We had mapped 3002:3000. The internal MCP URL appears to assume host port 3000, so the sandbox could not reach it, and the start request returned 500 with MCPError: MCP Connection Failure. Running the GUI on 3000 fixed it. If you remap the host port, expect that failure.
One warning is harmless. LiteLLM has no price entry for kimi-k3, so the cost reads 0.0 and the log prints Cost calculation failed: This model isn't mapped yet. Token counts are still recorded.
Waiting for client is a normal message now
The phrase still exists in the current GUI as a frontend string, TERMINAL$WAITING_FOR_CLIENT, shown while the terminal connects. Seeing it is not an error by itself.
Issue #9055, opened 11 June 2025 against a Docker install, was about the message never progressing. It was closed as stale, marked not planned, and never fixed in the tree. The exact phrase returns 90 issues, which is why old reports still surface. If the message never moves, upgrade before debugging.
A short triage
- On cloud, read the text after the sandbox id. No text means an older build; capacity means wait;
ImagePullBackOffmeans the image. - Locally, run
docker infofirst. If that fails, start Docker. - If
openhands servefails, check the TTY and port 3000. - If settings return 500, compare
agent_settings.schema_versionwith the build. - If the conversation hangs, look for the
Downloading Docker Imageloop and pre-pull the runtime image. - If the sandbox is up and the start request returns 500 with an MCP error, run the GUI on host port 3000 and retry.
- If the message is
Waiting for client to become readyand never moves, upgrade.
Scope
Tested on macOS with OrbStack, the 1.21 GUI image, and agent-server 1.36.0-python. The cloud path was not exercised directly; its details come from the issues. We got one successful agent reply on the default host port; the remapped-port failure is described above.
If you want an endpoint to point OpenHands at while you debug the sandbox, a new Wallaby Token account comes with $0.50 in trial credit. The minimum top-up is $20, and the rate stays 10% off the official list. Every request is itemized, so a failed run and a successful one are easy to tell apart.