Hmbown/CodeWhale · error · anyhow::Error

Refusing invalid base URL

Error message

Refusing invalid base URL '{display_base_url}'

What it means

validate_base_url_security parses the configured base URL with reqwest::Url::parse and refuses to build a client when the string is not a valid absolute URL. The URL is redacted for display (credentials stripped) before appearing in the message, so the error never leaks API keys. It is thrown at client construction so a malformed endpoint fails fast instead of producing confusing connection errors later.

Solutions

  1. Add an explicit scheme to the base URL in config.toml or the environment, e.g. https://api.openai.com/v1
  2. Trim whitespace and re-check the raw string around the configured value for typos or embedded characters
  3. If the value comes from an env var, echo it (masked) to confirm it resolves to a full URL and is non-empty
  4. If the URL is intentionally non-HTTP, confirm it matches what the provider expects (https or http to a loopback address)

Example fix

// before (config.toml)
base_url = "api.openai.com/v1"

// after
base_url = "https://api.openai.com/v1"
Defensive patterns

Strategy: validation

Validate before calling

fn base_url_is_valid(base_url: &str) -> bool {
    matches!(reqwest::Url::parse(base_url), Ok(u) if u.has_host() && matches!(u.scheme(), "https" | "http"))
}

if !base_url_is_valid(&cfg.base_url) {
    eprintln!("base_url must be an absolute http(s) URL, got: {:?}", cfg.base_url);
}

Prevention

When it happens

Trigger: Calling client construction/validate_base_url_security with a base_url string that reqwest::Url::parse rejects: missing scheme (e.g. "api.openai.com/v1" instead of "https://api.openai.com/v1"), whitespace, invalid characters, or a bare host with no scheme.

Common situations: Typing a base_url in config.toml without the https:// prefix; copying a URL with a trailing space or stray character; environment substitution producing an empty or partial URL; hostnames pasted without scheme when migrating provider configs.

Understand the failure class

Background: "Invalid URL" errors: why new URL(), URI.parse, and reqwest::Url reject your string — missing scheme, whitespace, and bad path format — this error's family across 39 libraries.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22). Data as JSON: /api/errors/6cdee261f6f5f9c5. Report an issue: GitHub.

Appendix: source

Thrown at crates/tui/src/client.rs:1079

    {
        return Err(CatalogRefreshError::InvalidResponse);
    }
    let mut stream = response.bytes_stream();
    let mut body = Vec::new();
    while let Some(chunk) = stream.next().await {
        let chunk = chunk.map_err(|_| CatalogRefreshError::Network)?;
        if body.len().saturating_add(chunk.len()) > max_bytes {
            return Err(CatalogRefreshError::InvalidResponse);
        }
        body.extend_from_slice(&chunk);
    }
    String::from_utf8(body).map_err(|_| CatalogRefreshError::InvalidResponse)
}

fn validate_base_url_security(base_url: &str, provider_allows_insecure_http: bool) -> Result<()> {
    let display_base_url = redact_url_for_display(base_url);
    let parsed = reqwest::Url::parse(base_url)
        .map_err(|_| anyhow::anyhow!("Refusing invalid base URL '{display_base_url}'"))?;
    let loopback = parsed.host_str().is_some_and(|host| {
        host.eq_ignore_ascii_case("localhost")
            || host
                .trim_matches(['[', ']'])
                .parse::<std::net::IpAddr>()
                .is_ok_and(|address| address.is_loopback())
    });
    if parsed.scheme() == "https" || (parsed.scheme() == "http" && loopback) {
        return Ok(());
    }

    if parsed.scheme() == "http" && provider_allows_insecure_http {
        logging::warn(
            "Using insecure HTTP base URL because this provider sets allow_insecure_http = true in config.toml",
        );
        return Ok(());
    }

View on GitHub (pinned to 73e0f67d83)