{"record":{"id":"9c40b44315475ff4","repo":"Hmbown/CodeWhale","slug":"codewhale-account-api-base-url-must-not-contain-a","errorCode":null,"errorMessage":"Codewhale account API base URL must not contain a query or fragment","messagePattern":"Codewhale account API base URL must not contain a query or fragment","errorType":"validation","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/cli/src/cloud.rs","lineNumber":767,"sourceCode":"        writeln!(out, \"Plan: {}\", printable(&user.plan))?;\n    }\n    writeln!(out, \"Profile: {}\", printable(profile))?;\n    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","sourceCodeStart":749,"sourceCodeEnd":785,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/0c42157ee52f9d55af2b506d71b46249910f77d3/crates/cli/src/cloud.rs#L749-L785","documentation":"validate_api_base requires a pure origin: any query string (?...) or fragment (#...) in the configured API base is rejected. The base is combined with fixed endpoint paths by the transport, so queries/fragments would either be silently dropped or corrupt the joined URL; rejecting them surfaces config typos immediately.","triggerScenarios":"Configuring --api-base 'https://api.codewhale.net?token=x' or 'https://host/#section'; pasting a deep link (with tracking params) instead of the bare origin.","commonSituations":"Copy-pasting a URL from a browser address bar that carries params, appending API keys as query params out of habit, leftover fragments from documentation anchors.","solutions":["Strip everything after the host[:port]: use https://api.codewhale.net.","Auth goes through `codewhale account login`, never query params.","Re-check the stored profile config after fixing the flag so the bad value does not persist."],"exampleFix":"# before\n--api-base 'https://api.codewhale.net?source=cli'\n\n# after\n--api-base https://api.codewhale.net","handlingStrategy":"validation","validationCode":"fn is_bare_origin(u: &url::Url) -> bool {\n    u.query().is_none() && u.fragment().is_none() && matches!(u.path(), \"\" | \"/\")\n}","typeGuard":"fn is_valid_api_base(value: &str) -> bool {\n    url::Url::parse(value.trim()).map(|u| {\n        is_bare_origin(&u) && u.username().is_empty() && u.password().is_none()\n            && (u.scheme() == \"https\" || (u.scheme() == \"http\" && is_loopback(u.host_str().unwrap_or(\"\"))))\n    }).unwrap_or(false)\n}","tryCatchPattern":null,"preventionTips":["Store only origins in api-base config.","Never append tokens as query params.","Validate the value in CI config checks before it reaches the CLI."],"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"}