aaif-goose/goose · error

apiKey is required when auth is enabled and no secret is…

Error message

apiKey is required when auth is enabled and no secret is stored

What it means

update_custom_provider reuses the existing api_key_env (or generates `<ID>_API_KEY` when empty). When requires_auth is true and the update params carry no api_key, it requires a secret to already exist under that name in Config; otherwise it errors. Conversely, when auth is disabled it deletes a previously generated key secret.

Solutions

  1. Pass api_key in the update params once — it is stored under api_key_env and future edits need no key
  2. Or store the secret manually under the existing api_key_env name (e.g. ACME_API_KEY) via the goose secret store
  3. If the provider genuinely needs no auth now, update with requires_auth: false (this also cleans up the generated secret)

Example fix

// before
let params = UpdateCustomProviderParams { requires_auth: true, api_key: None, .. };
update_custom_provider(params)?;

// after
let params = UpdateCustomProviderParams {
    requires_auth: true,
    api_key: Some(read_api_key_from_user()),
    ..
};
update_custom_provider(params)?;
Defensive patterns

Strategy: validation

Validate before calling

use goose::config::Config;

// before update_custom_provider with requires_auth and no api_key:
if params.requires_auth && params.api_key.is_none() {
    let key_name = if existing_config.api_key_env.is_empty() {
        generate_api_key_name(&params.id)
    } else {
        existing_config.api_key_env.clone()
    };
    if Config::global().get_secret::<String>(&key_name).is_err() {
        // prompt the user for the key and include it in the update params
    }
}

Try / catch

match update_custom_provider(params) {
    Err(e) if e.to_string().contains("apiKey is required when auth is enabled") => {
        // re-ask for the key, then retry the update with api_key: Some(...)
    }
    other => other?,
}

Prevention

When it happens

Trigger: Editing an existing auth-required provider without re-entering the key while the secret under its api_key_env is absent — e.g. the provider JSON was created by hand, the secret was deleted from goose config, or the key name drifted after an id change.

Common situations: Hand-authored provider JSON with requires_auth true but no matching secret ever stored; moving config to a new machine without migrating the secret store; secret removed via `goose configure`.

Related errors


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

Appendix: source

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

    Ok(provider_config)
}

pub fn update_custom_provider(params: UpdateCustomProviderParams) -> Result<()> {
    let loaded_provider = load_provider(&params.id)?;
    let existing_config = loaded_provider.config;
    let editable = loaded_provider.is_editable;

    let config = Config::global();
    let api_key_env = if params.requires_auth {
        let api_key_name = if existing_config.api_key_env.is_empty() {
            generate_api_key_name(&params.id)
        } else {
            existing_config.api_key_env.clone()
        };
        if let Some(api_key) = params.api_key.as_deref() {
            config.set_secret(&api_key_name, &api_key)?;
        } else if config.get_secret::<String>(&api_key_name).is_err() {
            return Err(anyhow::anyhow!(
                "apiKey is required when auth is enabled and no secret is stored"
            ));
        }
        api_key_name
    } else {
        if existing_config.api_key_env == generate_api_key_name(&params.id) {
            config.delete_secret(&existing_config.api_key_env)?;
        }
        String::new()
    };

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

View on GitHub (pinned to 3810898a74)