zeroclaw-labs/zeroclaw · error
Failed to open URL with default browser launchers; Brave com
Error message
Failed to open URL with default browser launchers; Brave compatibility fallback also failed. Last error: {last_error} What it means
On Linux, BrowserOpenTool tries five launchers in order (xdg-open, gio open, sensible-browser, brave-browser, brave) via run_browser_launcher, each under a 10-second timeout (BROWSER_OPEN_LAUNCH_TIMEOUT, browser_open.rs:8). This error means every launcher failed; last_error comes from the final attempt (brave) and is one of '<label> exited with status N', '<label> not runnable: <spawn error>', or '<label> timed out after 10s' (browser_open.rs:177-182). Because the five attempts run sequentially, a fully broken host can take up to ~50 seconds before this bail fires.
Source
Thrown at crates/zeroclaw-tools/src/browser_open.rs:234
"gio",
"sensible-browser",
"brave-browser",
"brave",
] {
let mut command = tokio::process::Command::new(cmd);
if cmd == "gio" {
command.arg("open");
}
command.arg(url);
let label = if cmd == "gio" { "gio open" } else { cmd };
match run_browser_launcher(command, label).await {
Ok(()) => return Ok(()),
Err(error) => last_error = error,
}
}
// TODO(compat): remove Brave fallback commands (brave-browser/brave) once default launcher coverage is validated.
anyhow::bail!(
"Failed to open URL with default browser launchers; Brave compatibility fallback also failed. Last error: {last_error}"
);
}
#[cfg(target_os = "windows")]
{
// Use direct process invocation (not `cmd /C start`) to avoid shell
// metacharacter interpretation in URLs (e.g. `&` in query strings).
let mut command = tokio::process::Command::new("rundll32");
command.arg("url.dll,FileProtocolHandler").arg(url);
let primary_error =
match run_browser_launcher(command, "rundll32 default-browser launcher").await {
Ok(()) => return Ok(()),
Err(error) => error,
};
// TODO(compat): remove Brave fallback after default-browser launch has been stable across Windows environments.
let mut brave_error = String::new();View on GitHub (pinned to 88bb9c8533)
Solutions
- Install xdg-utils (Debian/Ubuntu: apt-get install -y xdg-utils; Alpine: apk add xdg-utils) so xdg-open exists and succeeds first.
- Register a default browser on desktop systems: xdg-settings set default-webbrowser <browser.desktop> or fix gio/gnome associations.
- On headless hosts, stop routing browser_open there: disable the browser tool in config instead of retrying.
- Read the embedded last_error to classify the failure: 'not runnable' = missing binary (install it), 'timed out' = hung launcher (fix DE/portal), 'exited with status N' = launcher present but refused (check DISPLAY/XDG portal).
Example fix
# before: headless container, every launcher missing -> error # (Dockerfile with no xdg-utils) # after: provide the primary launcher in the image # Dockerfile RUN apt-get update && apt-get install -y --no-install-recommends xdg-utils
Defensive patterns
Strategy: fallback
Validate before calling
use std::path::Path;
fn has_linux_browser_launcher() -> bool {
let Ok(paths) = std::env::var("PATH") else { return false };
["xdg-open", "gio", "sensible-browser"].iter().any(|cmd| {
paths.split(':').any(|dir| !dir.is_empty() && Path::new(dir).join(cmd).exists())
})
}
// before registering the tool:
// if !has_linux_browser_launcher() { skip BrowserOpenTool or log a warning } Try / catch
match browser_tool.execute(json!({"url": url.clone()})).await {
Ok(result) if result.success => { /* opened */ }
Ok(result) => {
let detail = result.error.as_deref().unwrap_or_default();
eprintln!("browser_open failed ({detail}); open manually: {url}");
}
Err(e) => eprintln!("tool error: {e}"),
} Prevention
- Install xdg-utils in every Docker image / server baseline that will run the browser tool.
- Probe for a launcher on PATH at startup and skip registering BrowserOpenTool when none exists.
- Register a default browser (xdg-settings) on desktop deployments so xdg-open exits 0.
- Remember the 10s per-launcher timeout: on broken hosts the tool can block for up to ~50s before failing.
When it happens
Trigger: Calling the browser_open tool with an already-validated http(s) URL on Linux where: (1) none of the five launcher binaries are on PATH (spawn returns NotFound -> 'not runnable'), (2) xdg-open/gio exit non-zero because no desktop environment or default browser is registered, or (3) a launcher hangs (e.g. gio waiting on a stuck desktop portal) and hits the 10s timeout.
Common situations: Headless servers, Docker containers, WSL without GUI, CI runners, and minimal distros that lack the xdg-utils package or have no default web browser registered. Also seen after DE reinstalls where xdg-open exists but its browser associations are broken.
Related errors
- browser_open is not supported on this OS
- URL must include a host
- IPv6 hosts are not supported in browser_open
- URL must include a valid host
- rc-service restart failed: {}
AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23).
Data as JSON: /api/errors/2649d8409324748d.
Report an issue: GitHub.