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):