Hmbown/CodeWhale · error

Refusing to save an empty API key.

Error message

Refusing to save an empty API key.

What it means

When persisting a provider credential, `save_api_key` first trims the supplied key and refuses to store an empty string. Saving an empty key would silently break authentication, so the operation fails fast with this error.

Solutions

  1. Provide a non-empty API key when prompted or via the environment
  2. Check that the env var feeding the key is actually set (`echo $OPENAI_API_KEY`)
  3. Re-run the save command with the key quoted to avoid shell stripping

Example fix

// before
save_api_key("")
// after
let key = std::env::var("OPENAI_API_KEY")?;
if key.trim().is_empty() { return Err(anyhow!("missing OPENAI_API_KEY")); }
save_api_key(&key)
Defensive patterns

Strategy: validation

Validate before calling

if api_key.trim().is_empty() {
    return Err("provide a non-empty API key before saving");
}

Type guard

fn has_api_key(s: &str) -> bool { !s.trim().is_empty() }

Try / catch

match save_api_key(&key, slot, false) {
    Err(e) if e.to_string().contains("empty API key") => prompt_user_for_key(),
    other => other?,
}

Prevention

When it happens

Trigger: Calling the API-key save routine (e.g. `codewhale auth save` / TUI provider key setup) with an empty or whitespace-only `api_key` string.

Common situations: Pressing Enter without pasting a key in the TUI; an env var like `OPENAI_API_KEY` unset so the passed value is empty; a script interpolating an unset variable.

Understand the failure class

Background: "must not be empty", "cannot be empty" — required-field validation errors across open-source libraries — this error's family across 41 libraries.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22). Data as JSON: /api/errors/b935d1d45353302e. Report an issue: GitHub.

Appendix: source

Thrown at crates/tui/src/config.rs:11916

/// removed. When the secret-store write fails (OS permission denied, corrupt
/// or read-only file backend, etc.), the save fails loudly rather than writing
/// the key to plaintext `config.toml`.
///
/// Under `cfg(test)` the secret-store path is enabled only when the test sets
/// both an isolated `CODEWHALE_HOME` and an explicit backend, preventing unit
/// tests from touching the developer's real credential store.
pub fn save_api_key(api_key: &str) -> Result<SavedCredential> {
    save_root_api_key_for_secret_slot(api_key, "deepseek", true)
}

fn save_root_api_key_for_secret_slot(
    api_key: &str,
    secret_slot: &str,
    clear_deepseek_provider_slot: bool,
) -> Result<SavedCredential> {
    let trimmed = api_key.trim();
    if trimmed.is_empty() {
        anyhow::bail!("Refusing to save an empty API key.");
    }

    let path = credential_config_path().context("Failed to resolve config path for API key.")?;

    if let Some(secrets) = credential_secret_store() {
        // Same read-modify-write as the per-provider save below; hold the slot's
        // write lock across snapshot, store write, config write, and rollback.
        return crate::credentials::store::with_provider_write_lock(secret_slot, || {
            let prior_secret = secrets.get(secret_slot);
            match prior_secret.as_ref() {
                Ok(prior) => match secrets.set(secret_slot, trimmed) {
                    Ok(()) => {
                        if let Err(error) = save_root_api_key_metadata_without_plaintext(
                            &path,
                            clear_deepseek_provider_slot,
                        ) {
                            let current = secrets.get(secret_slot).map_err(|rollback| {
                        anyhow::anyhow!(

View on GitHub (pinned to 73e0f67d83)