aaif-goose/goose · error
Failed to construct URL: {}
Error message
Failed to construct URL: {} What it means
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.
Source
Thrown at crates/goose-providers/src/api_client.rs:466
}
pub async fn response_get(&self, path: &str) -> Result<Response> {
self.request(path).response_get().await
}
fn build_url(&self, path: &str) -> Result<url::Url> {
use url::Url;
let mut base_url =
Url::parse(&self.host).map_err(|e| anyhow::anyhow!("Invalid base URL: {}", e))?;
let base_path = base_url.path();
if !base_path.is_empty() && base_path != "/" && !base_path.ends_with('/') {
base_url.set_path(&format!("{}/", base_path));
}
let mut url = base_url
.join(path)
.map_err(|e| anyhow::anyhow!("Failed to construct URL: {}", e))?;
for (key, value) in &self.default_query {
url.query_pairs_mut().append_pair(key, value);
}
Ok(url)
}
}
impl<'a> ApiRequestBuilder<'a> {
pub fn header(mut self, key: &str, value: &str) -> Result<Self> {
let header_name = HeaderName::from_bytes(key.as_bytes())?;
let header_value = HeaderValue::from_str(value)?;
self.headers.insert(header_name, header_value);
Ok(self)
}
#[allow(dead_code)]View on GitHub (pinned to 3810898a74)
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
Example fix
// before
let client = ApiClient::new("data:text/plain,hello".into(), ...);
client.api_get("models"); // Failed to construct URL
// after
let client = ApiClient::new("https://api.example.com/v1/".into(), ...);
client.api_get("models"); Defensive patterns
Strategy: try-catch
Validate before calling
fn url_join_ok(host: &str, path: &str) -> bool {
url::Url::parse(host).ok().and_then(|b| b.join(path).ok()).is_some()
} Try / catch
match client.api_get(path).await {
Err(e) if e.to_string().contains("Failed to construct URL") => {
// path is bad, not the network: sanitize segments or reject the input
let safe: String = path.chars().map(|c| if c.is_control() || c == ' ' { '_' } else { c }).collect();
client.api_get(&safe).await
}
r => r,
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- External ACP backend URL is required
- External ACP backend URL must use http: or https:, got ${url
- External ACP backend URL must not include query parameters o
- External ACP backend URL must be the base URL before /acp
- Invalid base URL: {}
AI-assisted analysis of aaif-goose/goose@3810898a74 (2026-08-16).
Data as JSON: /api/errors/aefeac3bcf507aa3.
Report an issue: GitHub.