aaif-goose/goose · error

apiKey cannot be empty

Error message

apiKey cannot be empty

What it means

create_custom_provider stores the API key as a secret named `<ID>_API_KEY` (generate_api_key_name) when requires_auth is true. Before storing, it requires a non-blank api_key (None or whitespace-only is rejected with this error) so the secret is never written empty; nothing is persisted on failure.

Source

Thrown at crates/goose/src/config/declarative_providers.rs:177

    pub headers: Option<HashMap<String, String>>,
    pub requires_auth: bool,
    pub catalog_provider_id: Option<String>,
    pub base_path: Option<String>,
    pub preserves_thinking: Option<bool>,
}

pub fn create_custom_provider(
    params: CreateCustomProviderParams,
) -> Result<DeclarativeProviderConfig> {
    let id = generate_id(&params.display_name);
    validate_provider_id(&id)?;

    let api_key_env = if params.requires_auth {
        let api_key = params
            .api_key
            .as_deref()
            .filter(|api_key| !api_key.trim().is_empty())
            .ok_or_else(|| anyhow::anyhow!("apiKey cannot be empty"))?;
        let api_key_name = generate_api_key_name(&id);
        let config = Config::global();
        config.set_secret(&api_key_name, &api_key)?;
        api_key_name
    } else {
        String::new()
    };

    let model_infos: Vec<ModelInfo> = params
        .models
        .into_iter()
        .map(|name| ModelInfo::new(name, 128000))
        .collect();

    let engine = ProviderEngine::from_str(&params.engine)?;
    let preserves_thinking = params
        .preserves_thinking
        .unwrap_or_else(|| should_preserve_thinking_by_default(&engine));

View on GitHub (pinned to 3810898a74)

Solutions

  1. Supply a real api_key in the create params
  2. If the endpoint needs no auth (local gateways like Ollama), create the provider with requires_auth: false

Example fix

// before
let params = CreateCustomProviderParams { requires_auth: true, api_key: None, .. };

// after
let params = CreateCustomProviderParams {
    requires_auth: true,
    api_key: Some(std::env::var("ACME_API_KEY")?),
    ..
};
Defensive patterns

Strategy: validation

Validate before calling

let params = CreateCustomProviderParams { /* ... */ };
if params.requires_auth {
    let key = params.api_key.as_deref().unwrap_or("").trim();
    assert!(!key.is_empty(), "an API key is required for auth-enabled providers");
}

Type guard

fn has_auth_inputs(params: &CreateCustomProviderParams) -> bool {
    !params.requires_auth
        || params.api_key.as_deref().is_some_and(|k| !k.trim().is_empty())
}

Prevention

When it happens

Trigger: Calling create_custom_provider with CreateCustomProviderParams { requires_auth: true, api_key: None } or api_key: Some(" ") — the value is filtered on trim().is_empty().

Common situations: UI or script creating an auth-required provider before the user pasted the key; passing an unset environment variable; whitespace paste from a clipboard.

Related errors


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