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
- Provide a non-empty API key when prompted or via the environment
- Check that the env var feeding the key is actually set (`echo $OPENAI_API_KEY`)
- 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
- Check that the env var feeding the key is set before invoking save
- Trim and verify user input in TUI prompt flows
- Fail early with your own message when key input is blank
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
- api_key cannot be empty string
- agent profile provider cannot be empty
- agent profile provider must be a simple provider id
- API key contains invalid control characters
- API key id must be lowercase hex characters — the part…
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)