{"record":{"id":"2eaf47dc4b04c2cb","repo":"Hmbown/CodeWhale","slug":"codewhale-account-api-base-url-must-be-an-origin-w","errorCode":null,"errorMessage":"Codewhale account API base URL must be an origin without a path","messagePattern":"Codewhale account API base URL must be an origin without a path","errorType":"validation","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/cli/src/cloud.rs","lineNumber":770,"sourceCode":"    writeln!(out, \"API: {api_base}\")?;\n    Ok(())\n}\n\nstruct 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,","sourceCodeStart":752,"sourceCodeEnd":788,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/0c42157ee52f9d55af2b506d71b46249910f77d3/crates/cli/src/cloud.rs#L752-L788","documentation":"validate_api_base only accepts an origin: the URL path must be empty or exactly '/'. Any real path (e.g. /api or /codewhale) is rejected because the transport appends fixed endpoint paths itself and a base with a path would produce wrong request URLs.","triggerScenarios":"Setting --api-base https://example.com/api, https://host/codewhale/, or a reverse-proxy subpath URL; pasting an endpoint URL instead of the server root.","commonSituations":"Self-hosted/loopback test servers mounted under a subpath, users copying a full API endpoint from docs, proxies that require a path prefix (which must be handled at the proxy, not in api-base).","solutions":["Use the bare origin: https://host (optionally with port); the CLI adds /api/... paths itself.","If you need a path prefix, configure the proxy to rewrite at the root instead of encoding it in api-base.","For local testing, bind the test server at root, e.g. http://127.0.0.1:8080."],"exampleFix":"# before\n--api-base https://claude-proxy.internal/api\n\n# after\n--api-base https://claude-proxy.internal   # proxy rewrites /api/* to backend","handlingStrategy":"validation","validationCode":"fn has_no_path(u: &url::Url) -> bool { matches!(u.path(), \"\" | \"/\") }","typeGuard":"fn is_valid_api_base(value: &str) -> bool {\n    url::Url::parse(value.trim()).map(|u| {\n        matches!(u.path(), \"\" | \"/\") && u.query().is_none() && u.fragment().is_none()\n            && (u.scheme() == \"https\" || u.scheme() == \"http\" && is_loopback_host(u.host_str().unwrap_or(\"\")))\n    }).unwrap_or(false)\n}","tryCatchPattern":null,"preventionTips":["Enter the server root, not an endpoint or subpath.","Handle path prefixes at the reverse proxy, not in api-base.","Add a config lint that rejects paths in api-base."],"tags":["url-validation","cloud","cli","config"],"backgroundTag":"invalid-base-url","analyzedSha":"0c42157ee52f9d55af2b506d71b46249910f77d3","analyzedAt":"2026-08-20T21:50:45.477Z","schemaVersion":2},"datasetVersion":"2026-08-21T13:17:26.733Z"}