Improve Browser Docker runtime recovery

Clarify Browser settings around internal Docker vs A0 CLI host-browser runtimes. Add recovery guidance to host-browser failures so users can switch back to the internal Docker browser from settings or /browser container. Cover the recovery messaging in host-browser connector tests.

Alessandro committed May 18, 2026 at 02:00 UTC 27aa2d8550cfc346a4ab95ab15208542cace80b6
5 files changed +78 -12
plugins/_browser/helpers/connector_runtime.py
+15 -2
@@ -84,6 +84,10 @@ _HOST_BROWSER_REMOTE_DEBUGGING_HELP = (
84 'enable "Allow remote debugging for this browser instance", run `/browser host on`, '
85 "and retry."
86 )
87 +_DOCKER_BROWSER_RECOVERY_HELP = (
88 + "To use Agent Zero's internal Docker browser instead, open Browser settings and set "
89 + "Browser location to Internal Docker browser, or run `/browser container` from A0 CLI."
90 +)
91 _REMOTE_DEBUGGING_ERROR_TOKENS = (
92 "remote debugging",
93 "remote-debugging",
@@ -478,10 +482,19 @@ class ConnectorBrowserRuntime:
482 message = "Host browser operation failed"
483 normalized = message.lower()
484 if "chrome://inspect/#remote-debugging" in normalized:
481 - return message
485 + return _append_docker_browser_recovery(message)
486 if any(token in normalized for token in _REMOTE_DEBUGGING_ERROR_TOKENS):
483 - return f"{message}\n\n{_HOST_BROWSER_REMOTE_DEBUGGING_HELP}"
487 + return _append_docker_browser_recovery(
488 + f"{message}\n\n{_HOST_BROWSER_REMOTE_DEBUGGING_HELP}"
489 + )
490 + return _append_docker_browser_recovery(message)
491 +
492 +
493 +def _append_docker_browser_recovery(message: str) -> str:
494 + normalized = str(message or "").lower()
495 + if "internal docker browser" in normalized or "/browser container" in normalized:
496 return message
497 + return f"{message}\n\n{_DOCKER_BROWSER_RECOVERY_HELP}"
498
499
500 @lru_cache(maxsize=1)
plugins/_browser/helpers/selector.py
+9 -1
@@ -7,6 +7,13 @@ from plugins._browser.helpers.config import RUNTIME_BACKEND_KEY, get_browser_con
7 from plugins._browser.helpers.runtime import get_runtime as get_container_runtime
8
9
10 +DOCKER_BROWSER_RECOVERY_HELP = (
11 + "To use Agent Zero's internal Docker browser instead, open Browser settings and "
12 + "set Browser location to Internal Docker browser, or run `/browser container` "
13 + "from A0 CLI."
14 +)
15 +
16 +
17 async def get_tool_runtime(agent: Any):
18 context_id = str(agent.context.id)
19 config = get_browser_config(agent=agent)
@@ -29,7 +36,8 @@ async def get_tool_runtime(agent: Any):
36 + (f": {detail}" if detail else ".")
37 )
38 raise RepairableException(
32 - f"{message} Connect A0 CLI to this chat, allow host browser access, and retry."
39 + f"{message} Connect A0 CLI to this chat, allow host browser access, and retry. "
40 + f"{DOCKER_BROWSER_RECOVERY_HELP}"
41 )
42
43 return await get_container_runtime(context_id)
plugins/_browser/webui/browser-config-store.js
+11
@@ -179,6 +179,17 @@ export const store = createStore("browserConfig", {
179 return "Connect A0 CLI to use a host browser";
180 },
181
182 + browserRuntimeStatusLabel() {
183 + if (this.config?.runtime_backend !== "host_required") {
184 + return "Docker browser runs inside Agent Zero; A0 CLI host-browser status does not affect it.";
185 + }
186 + const label = this.hostBrowserConnectorLabel();
187 + if (label.startsWith("Connect A0 CLI") || label.includes("unavailable")) {
188 + return `${label}. Switch Browser location to Internal Docker browser to browse without A0 CLI.`;
189 + }
190 + return label;
191 + },
192 +
193 hasPaths() {
194 return this.pathCount() > 0;
195 },
plugins/_browser/webui/config.html
+24 -7
@@ -16,25 +16,39 @@
16 x-destroy="$store.browserConfig.cleanup()"
17 >
18 <div class="browser-config-card">
19 - <div class="section-title">Host Browser</div>
19 + <div class="section-title">Browser Runtime</div>
20 <div class="section-description">
21 - Use Chrome, Edge, or Chromium on this computer. Keep A0 CLI connected; the browser opens when the agent first needs it.
21 + Choose the internal Docker browser or a browser on the computer running A0 CLI.
22 </div>
23
24 <label class="browser-config-field">
25 <span class="browser-config-field-label">Browser location</span>
26 <select x-model="$store.browserConfig.config.runtime_backend">
27 - <option value="container">Docker browser</option>
28 - <option value="host_required">Bring Your Own Browser</option>
27 + <option value="container">Internal Docker browser</option>
28 + <option value="host_required">Host browser through A0 CLI</option>
29 </select>
30 + <span
31 + class="browser-config-field-help"
32 + x-show="$store.browserConfig.config.runtime_backend !== 'host_required'"
33 + >
34 + Runs inside Agent Zero's Docker environment. No A0 CLI connection or host browser setup is needed.
35 + </span>
36 <span
37 class="browser-config-field-help"
38 x-show="$store.browserConfig.config.runtime_backend === 'host_required'"
39 >
34 - Use Google Chrome or a Chromium family browser. Visit chrome://inspect/#remote-debugging and enable "Allow remote debugging".
40 + Uses Chrome, Edge, or Chromium on the A0 CLI host. Keep A0 CLI connected; the browser opens when the agent first needs it.
41 </span>
42 </label>
43
44 + <div
45 + class="browser-config-note browser-config-success"
46 + x-show="$store.browserConfig.config.runtime_backend !== 'host_required'"
47 + >
48 + <span class="material-symbols-outlined">deployed_code</span>
49 + <span>Internal Docker browser selected. Host-browser CLI settings are ignored for Browser tool runs.</span>
50 + </div>
51 +
52 <label
53 class="browser-config-field"
54 x-show="$store.browserConfig.config.runtime_backend === 'host_required'"
@@ -63,7 +77,10 @@
77 </span>
78 </div>
79
66 - <label class="browser-config-field">
80 + <label
81 + class="browser-config-field"
82 + x-show="$store.browserConfig.config.runtime_backend === 'host_required'"
83 + >
84 <span class="browser-config-field-label">Page content access</span>
85 <select x-model="$store.browserConfig.config.host_browser_privacy_policy">
86 <option value="enforce_local">Local models only</option>
@@ -75,7 +92,7 @@
92
93 <div class="browser-config-note">
94 <span class="material-symbols-outlined" :class="{ spinning: $store.browserConfig.hostBrowserStatusLoading }" x-text="$store.browserConfig.hostBrowserStatusLoading ? 'progress_activity' : 'captive_portal'"></span>
78 - <span x-text="$store.browserConfig.hostBrowserConnectorLabel()"></span>
95 + <span x-text="$store.browserConfig.browserRuntimeStatusLabel()"></span>
96 </div>
97 </div>
98
tests/test_host_browser_connector.py
+19 -2
@@ -32,9 +32,13 @@ def test_host_required_runtime_error_is_repairable(monkeypatch):
32 lambda agent=None: {"runtime_backend": "host_required"},
33 )
34
35 - with pytest.raises(browser_selector.RepairableException, match="Bring Your Own Browser"):
35 + with pytest.raises(browser_selector.RepairableException, match="Bring Your Own Browser") as exc_info:
36 asyncio.run(browser_selector.get_tool_runtime(_agent("ctx-host-required-missing")))
37
38 + message = str(exc_info.value)
39 + assert "Internal Docker browser" in message
40 + assert "/browser container" in message
41 +
42
43 def test_host_browser_metadata_selection_is_context_scoped():
44 sid = "sid-host-browser"
@@ -305,11 +309,24 @@ def test_connector_runtime_adds_remote_debugging_help_to_cdp_errors():
309 assert "chrome://inspect/#remote-debugging" in message
310 assert "Allow remote debugging for this browser instance" in message
311 assert "/browser host on" in message
312 + assert "Internal Docker browser" in message
313 + assert "/browser container" in message
314 already_helpful = (
315 "Open chrome://inspect/#remote-debugging and enable "
316 '"Allow remote debugging for this browser instance".'
317 )
312 - assert runtime._host_browser_error_message(already_helpful) == already_helpful
318 + already_helpful_message = runtime._host_browser_error_message(already_helpful)
319 + assert already_helpful in already_helpful_message
320 + assert "/browser container" in already_helpful_message
321 +
322 +
323 +def test_connector_runtime_adds_docker_recovery_to_host_errors():
324 + runtime = ConnectorBrowserRuntime("ctx-host", _agent("ctx-host"))
325 +
326 + message = runtime._host_browser_error_message("Host browser operation failed")
327 +
328 + assert "Internal Docker browser" in message
329 + assert "/browser container" in message
330
331
332 def test_host_browser_artifacts_materialize_inside_multi_results(monkeypatch, tmp_path):