purple-book-27215
06/07/2026, 10:07 PMmodern-fireman-99755
06/08/2026, 8:21 AMcurl only proves openhands-app can reach LM Studio, but the timeout you're hitting happens before any LLM call. The stack trace ends in wait_for_sandbox_running(), which means openhands-app created the agent-server sandbox container but its internal GET /alive probe never succeeded within 120s.
So LM Studio is a red herring here.
A couple of things to check, in order:
1. While the "Start from Scratch" is hanging, run in another shell:
docker ps -a --filter "name=oh-agent-server-" --format '{{.Names}}\t{{.Status}}\t{{.Image}}'
⢠No container at all ā openhands-app couldn't even start it. Check docker logs openhands-app for a Docker SDK error.
⢠Container Exited ā it crashed. docker logs <name> will show why.
⢠Container Up but timing out ā /alive isn't responding in time. From inside openhands-app:
docker exec -it openhands-app curl -v <http://host.docker.internal>:<host-port>/health
where <host-port> is what docker ps shows mapped to container port 8000.
2. Make sure you're using the v1 env-var names for the image. The legacy SANDBOX_RUNTIME_CONTAINER_IMAGE is no longer read in current OpenHands, the image now comes from these two, which must be set _together_:
-e AGENT_SERVER_IMAGE_REPOSITORY=<http://ghcr.io/openhands/agent-server|ghcr.io/openhands/agent-server>
-e AGENT_SERVER_IMAGE_TAG=1.26.0-python
If you set only one (or neither), it falls back to the built-in default, which on current main is exactly ghcr.io/openhands/agent-server:1.26.0-python. So if you had the legacy var set, OpenHands was pulling the default, not the image you thought. Pre-pull it so the 120s budget isn't eaten by the pull:
docker pull <http://ghcr.io/openhands/agent-server:1.26.0-python|ghcr.io/openhands/agent-server:1.26.0-python>
(Use the tag that matches your openhands version.)
3. If the host is just slow, bump the grace period (default 15s):
-e SANDBOX_STARTUP_GRACE_SECONDS=60
Note this only helps if the container is coming up near the limit, the 120s wait_for_sandbox_running ceiling itself isn't env-configurable, so if a cold image pull is what's blowing past 120s, the pre-pull in step 2 is the real fix.
If you can paste:
⢠output of the docker ps -a command above while it's hanging, and
⢠docker logs of the oh-agent-server-⦠container
we can tell in one look whether it's image, crash, or networking.purple-book-27215
06/08/2026, 11:35 PMmodern-fireman-99755
06/09/2026, 8:54 AMExited (0) is the key clue.
A startup crash (failed init, missing dependency, permission error) makes the agent-server exit with a non-zero code (uvicorn uses exit 3). An exit code of 0 means the process shut down cleanly, i.e. it was told to stop, not that it errored.
So this isn't the server failing to boot; something is stopping it ~10s after it starts. The ``500: Sandbox failed to start within 120s`` is just the downstream symptom: openhands-app keeps polling the sandbox's /health for 120s, but the container is already gone.
Two quick corrections so we don't chase ghosts:
⢠Your curl host.docker.internal:8000/health couldn't have worked. In the default (bridge) network mode, openhands-app publishes the sandbox's container port 8000 to a random host port ā not 8000 ā and
reaches it there. Nothing listens on host port 8000, so that "connection refused" tells us nothing.
⢠The sandbox container is created with remove=False, so even though it shows Exited, its logs are still there. That's exactly what we need.
Right after it exits, grab this (the name changes each attempt, so this picks the latest one):
NAME=$(docker ps -aq --filter "name=oh-agent-server-" --latest)
docker logs "$NAME" 2>&1 | tail -n 120
docker inspect "$NAME" --format 'exit={{.State.ExitCode}} oom={{.State.OOMKilled}} started ={{.State.StartedAt}} finished={{.State.FinishedAt}} err={{.State.Error}}'
What to look for in docker logs:
⢠Received signal SIGTERM/SIGINT, shutting down... ā the agent-server logs this when something sends it a stop signal. openhands-app does not stop a single sandbox on its own (it only pauses extra sandboxes beyond the limit, or removes on delete), so a signal here means something external is killing it: Docker Desktop, a `docker stop`/cleanup script, or the host. Worth checking whether anything on the host is issuing docker stop against oh-agent-server-*.
⢠Reaches Application startup complete / Uvicorn running on <http://0.0.0.0:8000> and then exits ā confirms it came up fine and was stopped afterwards (points to an external signal, not a code bug). The Error preloading chromium line, if present, is harmless ā the server continues past it.
⢠A Python traceback before exit ā a genuine startup failure, but that normally shows a non-zero exit code, so given the 0 it's less likely.
The docker inspect line also tells us how long it ran (`started`ā`finished`) and rules out an OOM kill (oom=true, which would be exit 137).
Paste the docker logs + docker inspect output and we'll know in one look whether it's an external stop signal, an OOM, or (less likely) a real crash.purple-book-27215
06/10/2026, 3:07 AMmodern-fireman-99755
06/10/2026, 2:42 PMSANDBOX_USER_ID (you set 1000). It writes the app-side event store to /.openhands/v1_conversations/⦠ā your ~/.openhands. That's the error you're hitting now.
⢠oh-agent-server-* runs as UID 10001 (openhands, baked into the agent-server image). SANDBOX_USER_ID is not passed through to it, so it's always 10001 no matter what you set. It writes conversations/ and bash_events/ into your mounted workspace ā /home/user1/dev/ws46_ai_agents. That's why chmod 777 on those dirs ā not the UID setting ā is what unblocked the sandbox.
So fix each side for its own UID:
App side (~/.openhands, owned by SANDBOX_USER_ID):
# set the app's UID to YOUR user instead of hardcoding 1000
-e SANDBOX_USER_ID=$(id -u)
# and make ~/.openhands match it
sudo chown -R $(id -u):$(id -g) /home/user1/.openhands
This is exactly the "manually adjust permissions of ~/.openhands for this user" note in the official docker-compose.
Workspace side (agent-server, UID 10001 ā not configurable):
Quickest: keep what you did: make the workspace writable by 10001 (chmod 777, or cleaner: setfacl -R -m u:10001:rwx -m d:u:10001:rwx /home/user1/dev/ws46_ai_agents).
Cleanest/permanent: build a custom agent-server image as your UID and point the image vars at it ā the Dockerfile takes a build arg:
docker build --build-arg UID=$(id -u) --build-arg GID=$(id -g) -t my-agent-server:1.26 ...
-e AGENT_SERVER_IMAGE_REPOSITORY=my-agent-server -e AGENT_SERVER_IMAGE_TAG=1.26
Then the sandbox runs as you and the workspace permission problem disappears.
Two 5-second confirmations that prove the above:
docker inspect $(docker ps -aq --filter "name=oh-agent-server-" --latest) --format '{{.Config.User}}' # expect openhands / 10001
docker exec -it openhands-app id # expect uid=<SANDBOX_USER_ID>
š¤purple-book-27215
06/12/2026, 12:43 PMmodern-fireman-99755
06/12/2026, 12:43 PMmodern-fireman-99755
06/12/2026, 12:43 PMmodern-fireman-99755
06/12/2026, 12:44 PMpurple-book-27215
06/12/2026, 1:04 PMmodern-fireman-99755
06/12/2026, 1:06 PM