aaif-goose/goose · error · anyhow::Error
Invalid base URL '{}': {}
Error message
Invalid base URL '{}': {} What it means
Thrown by from_declarative_config when the base_url field of a declarative provider cannot be parsed as a URL. Like error 280, the value passes through ensure_url_scheme first (http for loopback hosts, https otherwise), then url::Url::parse; failure is reported with the original config value and the parser error. It applies to provider definitions in config files rather than the OPENAI_BASE_URL env var.
Source
Thrown at crates/goose-providers/src/openai.rs:893
}
let api_key = if config.api_key_env.is_empty() {
None
} else {
match key_resolver.resolve_key(config.api_key_env.as_str()) {
Ok(key) => Some(key),
Err(err) => {
if config.requires_auth {
anyhow::bail!("missing required key {}: {}", config.api_key_env, err);
}
None
}
}
};
let normalized_base_url = ensure_url_scheme(&config.base_url);
let url = url::Url::parse(&normalized_base_url)
.map_err(|e| anyhow::anyhow!("Invalid base URL '{}': {}", config.base_url, e))?;
let host = url[..url::Position::BeforePath].to_string();
let base_path = if let Some(ref explicit_path) = config.base_path {
explicit_path.trim_start_matches('/').to_string()
} else {
derive_base_path(url.path())
};
let timeout_secs = config.timeout_seconds.unwrap_or(DEFAULT_TIMEOUT_SECONDS);
let auth = match api_key {
Some(key) if !key.is_empty() => AuthMethod::BearerToken(key),
_ => AuthMethod::NoAuth,
};
let mut api_client = ApiClient::with_timeout_and_tls(
host,
auth,
std::time::Duration::from_secs(timeout_secs),View on GitHub (pinned to 3810898a74)
Solutions
- Inspect the base_url value in the config file and fix the syntax error named by the parser message
- Include the scheme explicitly and verify host and port (https://gateway.example.com:8443/v1)
- Check for unsubstituted template placeholders or accidental line-wraps in the YAML string
- Test the URL standalone (curl or url::Url::parse) before restarting goose
Example fix
# before base_url: "https://my gateway.example.com/v1" # after base_url: "https://my-gateway.example.com/v1"
Defensive patterns
Strategy: validation
Validate before calling
url::Url::parse(&ensure_url_scheme(&cfg.base_url))
.map_err(|e| anyhow::anyhow!("fix base_url in provider config: {e}"))?; Try / catch
match OpenAiProviderBuilder::from_declarative_config(cfg.clone(), tls, resolver) {
Ok(b) => b,
Err(e) if e.to_string().contains("Invalid base URL") => {
eprintln!("provider '{}' has a bad base_url: {e}", cfg.name);
std::process::exit(2);
}
Err(e) => return Err(e),
} Prevention
- Template-substitute base_url before deploying configs and assert no '{{' remains
- Smoke-test configs by parsing them in a unit test
When it happens
Trigger: A provider entry whose base_url contains spaces, an invalid port, a missing host, or malformed percent-encoding, e.g. 'base_url: localhost 8080/v1' or 'base_url: https://[broken'.
Common situations: Hand-edited YAML introducing typos, URLs split across lines by accident, templated configs where a placeholder ({{host}}) was never substituted, or a trailing colon/backslash after the host.
Related errors
- Invalid OPENAI_BASE_URL '{}': {}
- Provider '{}' has dynamic_models: false but no static models
- Could not resolve model config: {e}
- Could not resolve model config: {error}
- External ACP backend URL is required
AI-assisted analysis of aaif-goose/goose@3810898a74 (2026-08-16).
Data as JSON: /api/errors/c980c87c8c4b5867.
Report an issue: GitHub.