{"record":{"id":"aefeac3bcf507aa3","repo":"aaif-goose/goose","slug":"failed-to-construct-url","errorCode":null,"errorMessage":"Failed to construct URL: {}","messagePattern":"Failed to construct URL: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/goose-providers/src/api_client.rs","lineNumber":466,"sourceCode":"    }\n\n    pub async fn response_get(&self, path: &str) -> Result<Response> {\n        self.request(path).response_get().await\n    }\n\n    fn build_url(&self, path: &str) -> Result<url::Url> {\n        use url::Url;\n        let mut base_url =\n            Url::parse(&self.host).map_err(|e| anyhow::anyhow!(\"Invalid base URL: {}\", e))?;\n\n        let base_path = base_url.path();\n        if !base_path.is_empty() && base_path != \"/\" && !base_path.ends_with('/') {\n            base_url.set_path(&format!(\"{}/\", base_path));\n        }\n\n        let mut url = base_url\n            .join(path)\n            .map_err(|e| anyhow::anyhow!(\"Failed to construct URL: {}\", e))?;\n\n        for (key, value) in &self.default_query {\n            url.query_pairs_mut().append_pair(key, value);\n        }\n\n        Ok(url)\n    }\n}\n\nimpl<'a> ApiRequestBuilder<'a> {\n    pub fn header(mut self, key: &str, value: &str) -> Result<Self> {\n        let header_name = HeaderName::from_bytes(key.as_bytes())?;\n        let header_value = HeaderValue::from_str(value)?;\n        self.headers.insert(header_name, header_value);\n        Ok(self)\n    }\n\n    #[allow(dead_code)]","sourceCodeStart":448,"sourceCodeEnd":484,"githubUrl":"https://github.com/aaif-goose/goose/blob/3810898a7447ec3299be72e223d3570a7aabf0ab/crates/goose-providers/src/api_client.rs#L448-L484","documentation":"After the base URL parsed successfully, base_url.join(path) failed while resolving the request path against it. In practice this only happens when the base URL is a 'cannot-be-a-base' URL (no hierarchical path, e.g. 'data:...', 'mailto:...', or a URL like 'https://host#frag' edge cases) or the path argument contains characters/structure that cannot combine into a valid URL.","triggerScenarios":"Constructing an ApiClient with a scheme that has no authority/path structure (data:, mailto:) so join() has nothing to resolve against, or passing a request path with invalid characters (spaces, unescaped '%' sequences) that break URL composition.","commonSituations":"Redirect-following or dynamically discovered endpoints feed a fully-formed absolute URL or a blob/data URI into a field expected to be an http(s) base; paths built from unencoded user input containing spaces or stray '%' signs.","solutions":["Use an http:// or https:// base URL with a normal host and path","URL-encode path segments coming from user input (percent-encode spaces and special characters)","If path is sometimes an absolute URL, branch: parse it directly instead of joining it onto the base"],"exampleFix":"// before\nlet client = ApiClient::new(\"data:text/plain,hello\".into(), ...);\nclient.api_get(\"models\"); // Failed to construct URL\n\n// after\nlet client = ApiClient::new(\"https://api.example.com/v1/\".into(), ...);\nclient.api_get(\"models\");","handlingStrategy":"try-catch","validationCode":"fn url_join_ok(host: &str, path: &str) -> bool {\n    url::Url::parse(host).ok().and_then(|b| b.join(path).ok()).is_some()\n}","typeGuard":null,"tryCatchPattern":"match client.api_get(path).await {\n    Err(e) if e.to_string().contains(\"Failed to construct URL\") => {\n        // path is bad, not the network: sanitize segments or reject the input\n        let safe: String = path.chars().map(|c| if c.is_control() || c == ' ' { '_' } else { c }).collect();\n        client.api_get(&safe).await\n    }\n    r => r,\n}","preventionTips":["Percent-encode any path segment interpolated from user/model input","Keep base URLs to http(s) with host+path; never feed data:/mailto: URIs as bases","If endpoints arrive as absolute URLs, branch on Url::parse(input) succeeding with a scheme, and use it directly"],"tags":["url","validation","configuration"],"backgroundTag":null,"analyzedSha":"3810898a7447ec3299be72e223d3570a7aabf0ab","analyzedAt":"2026-08-16T10:14:26.282Z","schemaVersion":2},"datasetVersion":"2026-08-16T13:17:31.715Z"}