{"record":{"id":"4dfb761d15348399","repo":"Hmbown/CodeWhale","slug":"custom-provider-base-url-must-be-an-http-s-url-with-a-host","errorCode":null,"errorMessage":"custom provider base URL must be an http(s) URL with a host","messagePattern":"custom provider base URL must be an http\\(s\\) URL with a host","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/tui/src/config_persistence.rs","lineNumber":717,"sourceCode":"        .chars()\n        .all(|ch| ch.is_ascii_alphanumeric() || matches!(ch, '_' | '-'))\n    {\n        bail!(\"custom provider name may only use letters, numbers, '-' and '_'\");\n    }\n    Ok(value.to_string())\n}\n\nfn normalize_custom_provider_base_url(raw: &str) -> anyhow::Result<String> {\n    use anyhow::bail;\n\n    let value = raw.trim().trim_end_matches('/');\n    if value.is_empty() {\n        bail!(\"custom provider base URL is required\");\n    }\n    let parsed = reqwest::Url::parse(value)\n        .map_err(|err| anyhow::anyhow!(\"custom provider base URL is invalid: {err}\"))?;\n    if !matches!(parsed.scheme(), \"http\" | \"https\") || parsed.host_str().is_none() {\n        bail!(\"custom provider base URL must be an http(s) URL with a host\");\n    }\n    Ok(value.to_string())\n}\n\nfn normalize_optional_custom_provider_field(raw: &str) -> Option<String> {\n    let value = raw.trim();\n    (!value.is_empty()).then(|| value.to_string())\n}\n\npub(crate) fn persist_hotbar_bindings(\n    config_path: Option<&Path>,\n    bindings: &[codewhale_config::HotbarBindingToml],\n) -> anyhow::Result<PathBuf> {\n    let path = config_toml_path(config_path)?;\n    mutate_config_document(&path, |doc| {\n        let table = doc.as_table_mut();\n        table.remove(\"hotbar\");\n        if bindings.is_empty() {","sourceCodeStart":699,"sourceCodeEnd":735,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/73e0f67d83c59909b571efdfc88c4bc28c309cb1/crates/tui/src/config_persistence.rs#L699-L735","documentation":"The base URL must parse with reqwest::Url and use the http or https scheme with a host. Anything else (missing scheme, ftp://, no host) bails so malformed endpoints never reach the config. A distinct message (\"custom provider base URL is invalid: {err}\") is returned when reqwest itself fails to parse; this bail covers a parseable URL with the wrong scheme or no host.","triggerScenarios":"Calling persist_custom_provider with e.g. \"api.example.com/v1\" (no scheme), \"ftp://host\", or a URL with no host component.","commonSituations":"Users omitting the https:// prefix; pasting a socket path or an internal hostname without a scheme.","solutions":["Prefix the value with https:// (or http://) if the scheme is missing.","Verify the URL parses and has an http/https scheme plus a host before calling.","Check the error text to distinguish parse failure from wrong-scheme/no-host."],"exampleFix":"// before\npersist_custom_provider(path, \"my_gateway\", \"api.example.com/v1\", ...)?;\n// after\npersist_custom_provider(path, \"my_gateway\", \"https://api.example.com/v1\", ...)?;","handlingStrategy":"validation","validationCode":"use reqwest::Url;\nlet u = Url::parse(url.trim().trim_end_matches('/'))?;\nif !matches!(u.scheme(), \"http\" | \"https\") || u.host_str().is_none() {\n    return Err(anyhow::anyhow!(\"must be http(s) URL with host\"));\n}","typeGuard":"fn is_http_url(raw: &str) -> bool {\n    reqwest::Url::parse(raw.trim().trim_end_matches('/'))\n        .map(|u| matches!(u.scheme(), \"http\" | \"https\") && u.host_str().is_some())\n        .unwrap_or(false)\n}","tryCatchPattern":"match persist_custom_provider(path, name, &url, ...) {\n    Err(e) if e.to_string().contains(\"http(s) URL\") || e.to_string().contains(\"base URL is invalid\") => show_url_error(&e),\n    other => other,\n}","preventionTips":["Always include the https:// scheme when entering endpoints.","Pre-validate with reqwest::Url::parse in the UI.","Reject non-http(s) schemes early."],"tags":["validation","url","custom-provider"],"backgroundTag":"invalid-url","analyzedSha":"73e0f67d83c59909b571efdfc88c4bc28c309cb1","analyzedAt":"2026-09-22T01:30:00.501Z","contentChangedAt":"2026-09-22T01:30:00.501Z","schemaVersion":2},"datasetVersion":"2026-09-22T11:17:16.035Z"}