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

missing required key {}: {}

Error message

missing required key {}: {}

What it means

Thrown by from_declarative_config when a provider declares an api_key_env variable and requires_auth: true, but the KeyResolver cannot resolve that environment variable. The message names the exact environment variable that is missing and the resolver's error, so the fix is to make that variable resolvable.

Source

Thrown at crates/goose-providers/src/openai.rs:884

        None
    };

    if config.dynamic_models == Some(false) && custom_models.is_none() {
        return Err(anyhow::anyhow!(
            "Provider '{}' has dynamic_models: false but no static models listed; \
             at least one entry in `models` is required.",
            config.name
        ));
    }

    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);

View on GitHub (pinned to 3810898a74)

Solutions

  1. Export the named variable before starting goose: export MY_GATEWAY_API_KEY=... (the message tells you the exact name)
  2. For desktop/launchd launches, set the variable in the service environment or the goose settings env block, not just the interactive shell
  3. Fix a typo'd api_key_env value so it matches the variable you actually set
  4. If the endpoint truly needs no auth (local llama.cpp/Ollama-style server), set requires_auth: false so a missing key is tolerated

Example fix

# before
export OPENAI_APIKEY=sk-...   # typo: resolver looks up OPENAI_API_KEY

# after
export OPENAI_API_KEY=sk-...
Defensive patterns

Strategy: validation

Validate before calling

let var = &cfg.api_key_env;
if cfg.requires_auth && !cfg.api_key_env.is_empty() && std::env::var(var).is_err() {
    return Err(anyhow::anyhow!("export {var} before starting goose"));
}

Try / catch

match OpenAiProviderBuilder::from_declarative_config(cfg, tls, resolver) {
    Ok(b) => b,
    Err(e) if e.to_string().starts_with("missing required key") => {
        eprintln!("auth config error: {e}");
        std::process::exit(2);
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: A declarative provider with 'api_key_env: MY_GATEWAY_API_KEY' and 'requires_auth: true' while MY_GATEWAY_API_KEY is not set in the environment where goose runs; also when the env var name has a typo (e.g. OPENAI_APIKEY vs OPENAI_API_KEY).

Common situations: Forgetting to export the key in the shell/desktop service context (launchd/GUI apps do not inherit .bashrc exports), CI secrets not passed to the job, .env not loaded, or pointing api_key_env at a differently-named secret.

Related errors


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