zeroclaw-labs/zeroclaw · error
URL must include a valid host
Error message
URL must include a valid host
What it means
After the earlier checks, extract_host splits the authority on ':', trims whitespace, strips trailing dots, and lowercases to obtain the host (browser_open.rs:312-318). If what remains is empty, the authority contained only a port, dots, or separators and no actual hostname, so this bail fires (browser_open.rs:320-322). It catches host-missing cases that slip past the empty-authority check, such as 'https://:8080'.
Source
Thrown at crates/zeroclaw-tools/src/browser_open.rs:321
if authority.contains('@') {
anyhow::bail!("URL userinfo is not allowed");
}
if authority.starts_with('[') {
anyhow::bail!("IPv6 hosts are not supported in browser_open");
}
let host = authority
.split(':')
.next()
.unwrap_or_default()
.trim()
.trim_end_matches('.')
.to_lowercase();
if host.is_empty() {
anyhow::bail!("URL must include a valid host");
}
Ok(host)
}
#[cfg(test)]
mod tests {
use super::*;
use zeroclaw_config::autonomy::AutonomyLevel;
use zeroclaw_config::policy::SecurityPolicy;
fn test_tool(allowed_domains: Vec<&str>) -> BrowserOpenTool {
let security = Arc::new(SecurityPolicy {
autonomy: AutonomyLevel::Supervised,
..SecurityPolicy::default()
});
BrowserOpenTool::new(
security,View on GitHub (pinned to 88bb9c8533)
Solutions
- Include a real hostname: 'https://service.internal:8080/x' instead of 'https://:8080/x'.
- Fix the URL construction so the host variable is resolved and non-empty before formatting.
- Add a startup assertion in your own code that the built URL has a non-blank host to catch regressions early.
Example fix
// before
let url = format!("https://:{}", port); // -> "https://:8080"
// after
let url = format!("https://{host}:{port}", host = "service.internal"); Defensive patterns
Strategy: validation
Validate before calling
fn url_host_is_substantive(url: &str) -> bool {
let Some(rest) = url
.strip_prefix("https://")
.or_else(|| url.strip_prefix("http://"))
else {
return false;
};
let authority = rest.split(['/', '?', '#']).next().unwrap_or("");
if authority.is_empty() || authority.contains('@') || authority.starts_with('[') {
return false;
}
let host = authority.split(':').next().unwrap_or("");
!host.trim().trim_end_matches('.').is_empty()
} Prevention
- Assert the host component resolves to a non-empty string before formatting URLs with ':port'.
- Watch for Option-rendered-as-empty host variables in format! calls.
- Run a URL lint (parse + host check) over config-generated URLs in tests.
When it happens
Trigger: Passing 'https://:8080/x' (port-only authority), 'https://.' (dot-only authority), or URLs where the host segment between scheme and ':' is blank. Almost always a URL-building bug that dropped the hostname but kept a port or punctuation.
Common situations: format! templates that interpolate an Option<&str> host as empty while a static ':port' suffix remains; configs where a host key is typo'd so lookup yields None-rendered-as-empty; hand-edited URLs missing the name.
Related errors
- URL must include a host
- IPv6 hosts are not supported in browser_open
- Failed to open URL with default browser launchers; Brave com
- browser_open is not supported on this OS
- URL userinfo is not allowed
AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23).
Data as JSON: /api/errors/3b8d08a7b9e575c3.
Report an issue: GitHub.