{"record":{"id":"8b4d3dc218c3d339","repo":"Hmbown/CodeWhale","slug":"codewhale-account-api-base-url-must-include-a-host","errorCode":null,"errorMessage":"Codewhale account API base URL must include a host","messagePattern":"Codewhale account API base URL must include a host","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/cli/src/cloud.rs","lineNumber":774,"sourceCode":"struct ValidatedApiBase {\n    url: Url,\n    display: String,\n}\n\nfn validate_api_base(value: &str) -> Result<ValidatedApiBase> {\n    let mut url = Url::parse(value.trim()).context(\"invalid Codewhale account API base URL\")?;\n    if !url.username().is_empty() || url.password().is_some() {\n        bail!(\"Codewhale account API base URL must not contain credentials\");\n    }\n    if url.query().is_some() || url.fragment().is_some() {\n        bail!(\"Codewhale account API base URL must not contain a query or fragment\");\n    }\n    if !matches!(url.path(), \"\" | \"/\") {\n        bail!(\"Codewhale account API base URL must be an origin without a path\");\n    }\n    let host = url\n        .host_str()\n        .ok_or_else(|| anyhow!(\"Codewhale account API base URL must include a host\"))?;\n    let allowed = url.scheme() == \"https\" || (url.scheme() == \"http\" && is_loopback_host(host));\n    if !allowed {\n        bail!(\n            \"Codewhale account API base URL must use HTTPS (loopback HTTP is allowed for testing)\"\n        );\n    }\n    url.set_path(\"/\");\n    let display = url.as_str().trim_end_matches('/').to_string();\n    Ok(ValidatedApiBase { url, display })\n}\n\nfn validate_verification_url(\n    value: &str,\n    api_base: &str,\n    user_code: &str,\n    complete: bool,\n) -> Result<String> {\n    let url =","sourceCodeStart":756,"sourceCodeEnd":792,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/8880682c63083a91624de936797efa3ce9e498fd/crates/cli/src/cloud.rs#L756-L792","documentation":"validate_api_base hardens the Codewhale account API base URL (config/env override of the default api.codewhale.net origin). After already rejecting embedded credentials, query/fragment, and any path beyond \"/\", it requires a host. Url::parse can succeed for non-special schemes with no authority (mailto:, data:, unix:), and those hostless URLs are rejected here.","triggerScenarios":"Setting the account API base to a hostless URL that still parses: \"mailto:support@codewhale.net\", \"data:text/plain,x\", \"unix:///run/codewhale.sock\", \"about:blank\". Reached via the api-base config key or the CODEWHALE_API_BASE-style override on any cloud command.","commonSituations":"Typo dropping the https:// scheme and pasting a bare URI; attempting to point the CLI at a unix socket; copy-pasting a data/mailto link into config.","solutions":["Set the full origin, e.g. https://api.codewhale.net (or http://127.0.0.1:PORT for loopback testing)","Unset the custom API base override to fall back to the canonical default","Check the config for stray characters or a missing scheme before the host"],"exampleFix":"# before\n codewhale cloud login --api-base unix:///run/cw.sock   # error: must include a host\n\n# after\n codewhale cloud login --api-base https://api.codewhale.net","handlingStrategy":"validation","validationCode":"fn is_valid_api_base(value: &str) -> bool {\n    let Ok(url) = url::Url::parse(value.trim()) else { return false };\n    url.host_str().is_some_and(|h| !h.is_empty())\n        && url.username().is_empty()\n        && url.password().is_none()\n        && !url.query().is_some()\n        && !url.fragment().is_some()\n        && matches!(url.path(), \"\" | \"/\")\n        && (url.scheme() == \"https\" || url.scheme() == \"http\" && h_is_loopback(url))\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Always configure the api base as a scheme + host origin (https://api.codewhale.net)","Validate override values in CI before deploying configs to machines","Do not attempt unix sockets or scheme-less hosts — they are rejected by design"],"tags":["cli","cloud","url","config-validation","rust"],"backgroundTag":null,"analyzedSha":"8880682c63083a91624de936797efa3ce9e498fd","analyzedAt":"2026-08-16T11:31:27.956Z","schemaVersion":2},"datasetVersion":"2026-08-16T13:17:31.715Z"}