flaky-engineer-19286
12/17/2025, 8:37 AMcalm-artist-7206
12/17/2025, 12:54 PMcalm-artist-7206
12/17/2025, 12:56 PMcalm-artist-7206
12/17/2025, 12:57 PMcalm-artist-7206
12/17/2025, 1:06 PMnutritious-balloon-65098
12/17/2025, 1:06 PMnutritious-balloon-65098
12/17/2025, 1:25 PMflaky-engineer-19286
12/17/2025, 1:26 PMcalm-artist-7206
12/17/2025, 1:27 PMcalm-artist-7206
12/17/2025, 1:27 PMflaky-engineer-19286
12/17/2025, 1:28 PMnutritious-balloon-65098
12/17/2025, 1:39 PM{agent_server_url}/alive → {"status":"ok"}
▪︎ GET {agent_server_url}/server_info
• Committed and pushed the fix to the same branch and updated the draft PR.
PR and branch
• File: openhands/usage/developers/v1-local-agent-server.mdx
• Branch: docs/v1-local-agent-server-guide
• PR (draft): https://github.com/OpenHands/docs/pull/194
• Latest commit: “docs: fix MDX code span for alive/server_info to satisfy link checker”
Checklist (complete)
• Use only V1 execution paths (app_server) and verify end-to-end flow to agent-server: Done
• Add guide covering:
◦ Running app-server with a purely local software-agent-sdk agent-server (no Docker, no remote API): Done
◦ Building a custom agent with the SDK and integrating it locally: Done
• Include GitHub source links with link text to the main source locations: Done
• Open a docs PR with the guide: Done (draft PR)
• Changes concise: Yes (one new MDX file, one minimal fix commit). No extraneous changes.
Open item (naming)
• Your note about removing “v1” from the filename is acknowledged. I can rename the file to openhands/usage/developers/local-agent-server.mdx and update the PR. Would you like me to proceed with the rename and re-run link checks?
<sub>View full conversation</sub>flaky-engineer-19286
12/18/2025, 10:28 AMexport INSTALL_DOCKER=0 # No Docker for this guide
export RUNTIME=local # Force process-based sandbox (spawns local agent-server)
# optional: export ENABLE_V1=1 # V1 is enabled by default; set explicitly if you disabled it elsewhere
make build
to install the OpenHands latest repo.
But when I ran
make run FRONTEND_PORT=12000 FRONTEND_HOST=0.0.0.0 BACKEND_HOST=0.0.0.0
It throws an error like this:
Running the app...
Starting backend server...
Waiting for the backend to start...
18:18:59 - openhands:INFO: utils.py:152 - config.toml not found: [Errno 2] No such file or directory: 'config.toml'. Toml values have not been applied.
18:18:59 - openhands:INFO: server_config.py:55 - Using config class None
/home/wsy0227/.cache/pypoetry/virtualenvs/openhands-ai-XTtcVfrc-py3.12/lib/python3.12/site-packages/fastmcp/server/server.py:255: DeprecationWarning: Providing `stateless_http` when creating a server is deprecated. Provide it when calling `run` or as a global setting instead.
self._handle_deprecated_settings(
INFO: Started server process [1967469]
INFO: Waiting for application startup.
/home/wsy0227/debugging_repo/OpenHands/openhands/runtime/impl/local/local_runtime.py:111: DeprecationWarning: Session.attached_pane() is deprecated in favor of Session.active_pane()
pane = session.attached_pane
/home/wsy0227/debugging_repo/OpenHands/openhands/runtime/impl/local/local_runtime.py:114: DeprecationWarning: Session.kill_session() is deprecated in favor of Session.kill()
session.kill_session()
Process Process-1:
Traceback (most recent call last):
File "/mnt/disk1/conda_envs/openhands-localgui/lib/python3.12/multiprocessing/process.py", line 314, in _bootstrap
self.run()
File "/mnt/disk1/conda_envs/openhands-localgui/lib/python3.12/multiprocessing/process.py", line 108, in run
self._target(*self._args, **self._kwargs)
File "/home/wsy0227/debugging_repo/OpenHands/openhands/runtime/browser/browser_env.py", line 101, in browser_process
obs, info = env.reset()
^^^^^^^^^^^
File "/home/wsy0227/.cache/pypoetry/virtualenvs/openhands-ai-XTtcVfrc-py3.12/lib/python3.12/site-packages/gymnasium/wrappers/common.py", line 400, in reset
return super().reset(seed=seed, options=options)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/wsy0227/.cache/pypoetry/virtualenvs/openhands-ai-XTtcVfrc-py3.12/lib/python3.12/site-packages/gymnasium/core.py", line 333, in reset
return self.env.reset(seed=seed, options=options)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/wsy0227/.cache/pypoetry/virtualenvs/openhands-ai-XTtcVfrc-py3.12/lib/python3.12/site-packages/browsergym/core/env.py", line 232, in reset
self.browser = pw.chromium.launch(
^^^^^^^^^^^^^^^^^^^
File "/home/wsy0227/.cache/pypoetry/virtualenvs/openhands-ai-XTtcVfrc-py3.12/lib/python3.12/site-packages/playwright/sync_api/_generated.py", line 14502, in launch
self._sync(
File "/home/wsy0227/.cache/pypoetry/virtualenvs/openhands-ai-XTtcVfrc-py3.12/lib/python3.12/site-packages/playwright/_impl/_sync_base.py", line 115, in _sync
return task.result()
^^^^^^^^^^^^^
File "/home/wsy0227/.cache/pypoetry/virtualenvs/openhands-ai-XTtcVfrc-py3.12/lib/python3.12/site-packages/playwright/_impl/_browser_type.py", line 98, in launch
await self._channel.send(
File "/home/wsy0227/.cache/pypoetry/virtualenvs/openhands-ai-XTtcVfrc-py3.12/lib/python3.12/site-packages/playwright/_impl/_connection.py", line 69, in send
return await self._connection.wrap_api_call(
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/wsy0227/.cache/pypoetry/virtualenvs/openhands-ai-XTtcVfrc-py3.12/lib/python3.12/site-packages/playwright/_impl/_connection.py", line 558, in wrap_api_call
raise rewrite_error(error, f"{parsed_st['apiName']}: {error}") from None
playwright._impl._errors.Error: BrowserType.launch: EACCES: permission denied, mkdir '/workspace'
I think this is an issue with the V0 version where the agent runs are managed by runtime instead of sandboxes. When the runtime is not run on dockers, /workspace is not accessible to the BrowserGym for cache downloading. I have an issue and PR on this regarding V0 runtime: https://github.com/OpenHands/OpenHands/pull/12015 .
My question is: since OpenHands now uses the sandbox-based approach to manage agent execution, shouldn’t this kind of error go away? My understanding is that EACCES: permission denied, mkdir '/workspace' is mainly a V0 local-runtime issue (where runs are managed by the runtime directly rather than sandboxes), as described in my previous issue/PR.
So I’m wondering which of the following is true:
1. My startup/config is still inadvertently triggering the V0 runtime path instead of the sandbox-based (V1) path; or
2. V1 still relies on the same BrowserGym/Playwright setup that tries to use /workspace (e.g., for downloads/cache), so the error can still happen even under V1, in this case I think my previous PR still appliescalm-artist-7206
12/18/2025, 11:26 AMflaky-engineer-19286
12/18/2025, 11:27 AMcalm-artist-7206
12/18/2025, 11:27 AMflaky-engineer-19286
12/18/2025, 11:27 AMflaky-engineer-19286
12/18/2025, 11:28 AMcalm-artist-7206
12/18/2025, 11:29 AMflaky-engineer-19286
12/18/2025, 11:30 AMcalm-artist-7206
12/18/2025, 11:39 AMcalm-artist-7206
12/18/2025, 11:39 AMflaky-engineer-19286
12/18/2025, 11:40 AMflaky-engineer-19286
12/18/2025, 3:06 PMBrowserGym/Playwright (and /workspace) errors even though the project is intended to be V1-only now.
What I expected
If the app is truly “V1-only” (software-agent-sdk agent-server + sandbox-managed execution), then the legacy runtime BrowserGym code (openhands/runtime/browser/browser_env.py) shouldn’t be executed during normal server startup / V1 conversation start.
What I’m observing
When I export RUNTIME=local and start the server, BrowserEnv() is initialized and openhands/runtime/browser/browser_env.py runs (which previously led to EACCES: permission denied, mkdir '/workspace' on non-docker local setups).
Why this seems to happen (code path)
It looks like RUNTIME=local still affects the legacy runtime initialization at server startup:
1. RUNTIME=local → OpenHandsConfig.runtime = "local" via env loading
2. On server startup, the FastAPI lifespan always enters the conversation manager:
- openhands/server/app.py:56 (async with conversation_manager:)
3. The conversation manager unconditionally initializes the selected runtime:
- openhands/server/conversation_manager/standalone_conversation_manager.py:82
(get_runtime_cls(self.config.runtime).setup(self.config))
4. If runtime == "local", this calls LocalRuntime.setup(), which runs dependency checks and instantiates `BrowserEnv()`:
- openhands/runtime/impl/local/local_runtime.py:398 (LocalRuntime.setup)
- `openhands/runtime/impl/local/local_runtime.py:119`–`123` (browser = BrowserEnv())
→ which executes openhands/runtime/browser/browser_env.py
So even if V1 is enabled/used for conversations, setting RUNTIME=local appears to pull the process into legacy runtime setup, which then triggers BrowserGym.calm-artist-7206
12/18/2025, 3:11 PMstandalone_conversation_manager < this one I thought it shouldn’t be activeflaky-engineer-19286
12/18/2025, 3:13 PMexport RUNTIME=local , cuz when I have unset RUNTIME I don't see any errors from BrowserGymcalm-artist-7206
12/18/2025, 3:13 PMcalm-artist-7206
12/18/2025, 3:14 PMflaky-engineer-19286
12/18/2025, 3:15 PMRUNTIME and perhaps it falsely triggered v0calm-artist-7206
12/18/2025, 3:16 PMcalm-artist-7206
12/18/2025, 3:16 PMflaky-engineer-19286
12/18/2025, 3:16 PMRUNTIME env var name collision.
- In OSS server startup, we still go through StandaloneConversationManager.__aenter__, which calls get_runtime_cls(config.runtime).setup(...). So if I set RUNTIME=local, it sets OpenHandsConfig.runtime=local and triggers the legacy LocalRuntime.setup() (and its BrowserEnv/BrowserGym dependency checks).
- Separately, the v1 app_server sandbox fallback also checks os.getenv('RUNTIME') to decide between docker/process/remote sandbox.
So when I set RUNTIME=local intending “v1 process sandbox”, it also unintentionally triggers the legacy runtime path and BrowserGym. When I unset RUNTIME, that legacy path doesn’t get forced to LocalRuntime, so the BrowserGym errors disappear.calm-artist-7206
12/18/2025, 3:17 PMRUNTIME = ‘process’ ?flaky-engineer-19286
12/18/2025, 3:17 PMflaky-engineer-19286
12/18/2025, 3:18 PMcalm-artist-7206
12/18/2025, 3:18 PMcalm-artist-7206
12/18/2025, 3:19 PMflaky-engineer-19286
12/18/2025, 3:19 PMRUNTIME=processflaky-engineer-19286
12/18/2025, 3:21 PMflaky-engineer-19286
12/18/2025, 3:26 PMRUNTIME collision, what direction do you prefer for a PR?
1. document that V1 local mode is RUNTIME=process and add a warning when RUNTIME=local is used (since it triggers legacy LocalRuntime/BrowserGym setup).
2. in V1 server mode, treat RUNTIME=local as process sandbox and prevent it from selecting legacy LocalRuntime (or at least warn + ignore).
3. split env vars (e.g. OH_SANDBOX_MODE for v1, OH_LEGACY_RUNTIME for legacy) and deprecate the old RUNTIME usage.
Also: do you expect StandaloneConversationManager to be inactive in OSS when V1 is enabled? If so, we can add a V1-only server mode switch that skips the legacy lifespan/routes.