aaif-goose/goose · error · anyhow::Error

missing required key {}: {}

Error message

missing required key {}: {}

What it means

The Ollama declarative config names an api key environment variable via api_key_env, and the key resolver (which reads that env var) failed while the config also sets requires_auth: true — so the missing key is fatal instead of being ignored. The message names both the env var and the underlying resolver error. If requires_auth were false (or unset), a failing resolve would just mean no auth header, which suits local Ollama servers that don't check keys.

Source

Thrown at crates/goose-providers/src/ollama.rs:323

    let mut base_url = Url::parse(&base)
        .map_err(|e| anyhow::anyhow!("Invalid base URL '{}': {}", config.base_url, e))?;

    let is_localhost = matches!(base_url.host_str(), Some("localhost" | "127.0.0.1" | "::1"));

    if base_url.port().is_none() && !base_has_scheme && is_localhost {
        base_url
            .set_port(Some(OLLAMA_DEFAULT_PORT))
            .map_err(|_| anyhow::anyhow!("Failed to set default port"))?;
    }

    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 auth = match api_key {
        Some(key) if !key.is_empty() => AuthMethod::BearerToken(key),
        _ => AuthMethod::NoAuth,
    };

    let mut api_client =
        ApiClient::with_timeout_and_tls(base_url.to_string(), auth, timeout, tls_config)?;

    if let Some(headers) = &config.headers {
        let mut header_map = reqwest::header::HeaderMap::new();
        for (key, value) in headers {
            let header_name = reqwest::header::HeaderName::from_bytes(key.as_bytes())?;

View on GitHub (pinned to 3810898a74)

Solutions

  1. Export the named variable in goose's environment: export OLLAMA_API_KEY=... (match the name in api_key_env exactly, case included)
  2. For a local Ollama with no auth in front, set "requires_auth": false so a missing key is tolerated
  3. If a proxy is optional, prefer requires_auth: false and supply the key only when present
  4. Double-check api_key_env spelling — a var that never exists will always fail when required

Example fix

// before
{ "name": "proxied-ollama", "engine": "ollama", "base_url": "https://llm.internal:11434",
  "api_key_env": "OLLAMA_API_KEY", "requires_auth": true }
// OLLAMA_API_KEY not exported -> missing required key OLLAMA_API_KEY

// after
export OLLAMA_API_KEY=sk-...   # or, for plain local servers:
// "requires_auth": false
Defensive patterns

Strategy: validation

Validate before calling

fn ollama_auth_satisfiable(api_key_env: &str, requires_auth: bool) -> anyhow::Result<()> {
    if requires_auth && !api_key_env.is_empty() && std::env::var(api_key_env).is_err() {
        anyhow::bail!("export {api_key_env} (required by this provider) or set requires_auth=false");
    }
    Ok(())
}

Try / catch

// Pre-flight all required key env vars once at startup and list them together:
let missing: Vec<_> = providers.iter()
    .filter(|p| p.requires_auth && std::env::var(&p.api_key_env).is_err())
    .map(|p| p.api_key_env.clone()).collect();
anyhow::ensure!(missing.is_empty(), "missing required API key env vars: {}", missing.join(", "));

Prevention

When it happens

Trigger: A declarative Ollama provider JSON with "api_key_env": "OLLAMA_API_KEY" (any name) and "requires_auth": true, run where that environment variable is not set — e.g. routing through an auth-gating proxy (LiteLLM, corporate gateway) in CI that lacks the secret.

Common situations: Teams front Ollama with an authenticating proxy and forget the secret in CI/service environments; the env var name in JSON has a typo or case mismatch versus what's exported; local setups copied a remote team's config but don't run the proxy, so the var was never needed before.

Related errors


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