Hmbown/CodeWhale · error · anyhow::Error

custom provider base URL is invalid

Error message

custom provider base URL is invalid: {err}

What it means

normalize_custom_provider_base_url validates the base URL supplied for a custom provider. A URL that reqwest::Url::parse rejects (bad scheme, missing host, malformed characters) produces this error before persist_custom_provider writes anything.

Solutions

  1. Include an explicit http:// or https:// scheme, e.g. http://localhost:11434/v1.
  2. Remove trailing slashes, spaces, or invisible characters from the URL.
  3. Verify the URL has a host (IP or domain); a bare path or empty value is rejected.

Example fix

// before
let url = normalize_custom_provider_base_url("localhost:8080/v1")?;
// after
let url = normalize_custom_provider_base_url("http://localhost:8080/v1")?;
Defensive patterns

Strategy: validation

Validate before calling

fn valid_custom_base_url(raw: &str) -> bool {
    let v = raw.trim().trim_end_matches('/');
    !v.is_empty()
        && reqwest::Url::parse(v).map(|u| matches!(u.scheme(), "http" | "https") && u.host_str().is_some()).unwrap_or(false)
}

Try / catch

match normalize_custom_provider_base_url(input) {
    Err(e) => eprintln!("fix base URL (need http(s) + host): {e}"),
    Ok(url) => persist_custom_provider(&url, ...)?,
}

Prevention

When it happens

Trigger: persist_custom_provider is called with a base URL string that fails reqwest's URL parsing — e.g. 'myserver:8080/v1' (no scheme), 'http//x', or a URL containing control characters.

Common situations: Typing an endpoint without http:// or https://; pasting a URL with embedded whitespace or invisible characters; entering 'localhost:11434' without a scheme; trailing slashes are tolerated but a missing host is not.

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/5c395107eb9014cb. Report an issue: GitHub.

Appendix: source

Thrown at crates/tui/src/config_persistence.rs:715

    }
    if !value
        .chars()
        .all(|ch| ch.is_ascii_alphanumeric() || matches!(ch, '_' | '-'))
    {
        bail!("custom provider name may only use letters, numbers, '-' and '_'");
    }
    Ok(value.to_string())
}

fn normalize_custom_provider_base_url(raw: &str) -> anyhow::Result<String> {
    use anyhow::bail;

    let value = raw.trim().trim_end_matches('/');
    if value.is_empty() {
        bail!("custom provider base URL is required");
    }
    let parsed = reqwest::Url::parse(value)
        .map_err(|err| anyhow::anyhow!("custom provider base URL is invalid: {err}"))?;
    if !matches!(parsed.scheme(), "http" | "https") || parsed.host_str().is_none() {
        bail!("custom provider base URL must be an http(s) URL with a host");
    }
    Ok(value.to_string())
}

fn normalize_optional_custom_provider_field(raw: &str) -> Option<String> {
    let value = raw.trim();
    (!value.is_empty()).then(|| value.to_string())
}

pub(crate) fn persist_hotbar_bindings(
    config_path: Option<&Path>,
    bindings: &[codewhale_config::HotbarBindingToml],
) -> anyhow::Result<PathBuf> {
    let path = config_toml_path(config_path)?;
    mutate_config_document(&path, |doc| {
        let table = doc.as_table_mut();

View on GitHub (pinned to 73e0f67d83)