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

  1. Inspect the base_url value in the config file and fix the syntax error named by the parser message
  2. Include the scheme explicitly and verify host and port (https://gateway.example.com:8443/v1)
  3. Check for unsubstituted template placeholders or accidental line-wraps in the YAML string
  4. 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

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


AI-assisted analysis of aaif-goose/goose@3810898a74 (2026-08-16). Data as JSON: /api/errors/c980c87c8c4b5867. Report an issue: GitHub.