zeroclaw-labs/zeroclaw · error

URL must include a host

Error message

URL must include a host

What it means

extract_host strips the http:// or https:// prefix and takes everything before the first '/', '?', or '#' as the authority (browser_open.rs:289-298). If that component is empty, the URL names no host at all and this bail fires (browser_open.rs:300-302). It is the earliest structural host check in browser_open's URL validation.

Source

Thrown at crates/zeroclaw-tools/src/browser_open.rs:301

                    .with_attrs(::serde_json::json!({"url": url})),
                "browser_open: unsupported URL scheme rejected"
            );
            anyhow::Error::msg("Only http:// or https:// URLs are allowed")
        })?;

    let authority = rest.split(['/', '?', '#']).next().ok_or_else(|| {
        ::zeroclaw_log::record!(
            WARN,
            ::zeroclaw_log::Event::new(module_path!(), ::zeroclaw_log::Action::Reject)
                .with_outcome(::zeroclaw_log::EventOutcome::Failure)
                .with_attrs(::serde_json::json!({"url": url})),
            "browser_open: invalid URL"
        );
        anyhow::Error::msg("Invalid URL")
    })?;

    if authority.is_empty() {
        anyhow::bail!("URL must include a host");
    }

    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();

View on GitHub (pinned to 88bb9c8533)

Solutions

  1. Fix the caller to include a real host: 'https://example.com/path' instead of 'https:///path'.
  2. If the URL is built from parts, assert the host component is non-empty before formatting.
  3. Check the template/variable that produced the URL for an unset or empty hostname.

Example fix

// before
let url = format!("https://{}/report", maybe_empty_host);

// after
anyhow::ensure!(!maybe_empty_host.is_empty(), "host is required");
let url = format!("https://{maybe_empty_host}/report");
Defensive patterns

Strategy: validation

Validate before calling

fn has_url_host(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("");
    !authority.is_empty()
}

Prevention

When it happens

Trigger: Passing a URL where nothing follows the scheme before the next delimiter: 'https://' alone, 'https:///path' (empty netloc), 'https://?q=1', or 'http://#fragment'. Typically produced by string templating that interpolates an empty host variable: format!("https://{}/x", host) with host == "".

Common situations: LLM-generated URLs that truncate after the scheme, config-driven URL builders with a missing hostname key, and copy-paste of scheme-only prefixes during testing.

Related errors


AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23). Data as JSON: /api/errors/fdfbe75e81d6288c. Report an issue: GitHub.