Hmbown/CodeWhale · error
api_key cannot be empty string
Error message
api_key cannot be empty string
What it means
An `api_key` present in config must not be blank. Empty or whitespace-only keys would be sent as credentials and fail downstream auth confusingly, so validation rejects them up front. Thrown at config.rs:5110.
Solutions
- Set api_key to the real key, or remove the field entirely to use env-based credentials.
- Verify the env var feeding the key is non-empty (e.g. GEMINI_API_KEY).
- Delete the blank key line rather than leaving `api_key = ""`.
Example fix
// before api_key = "" // after # api_key removed; key read from environment
Defensive patterns
Strategy: validation
Validate before calling
if let Some(k) = &config.api_key {
if k.trim().is_empty() { return Err("api_key must be non-empty or omitted"); }
} Prevention
- Remove blank api_key fields instead of empty-string placeholders.
- Fail fast in scripts when the credential env var is empty before interpolation.
When it happens
Trigger: Config has `api_key = ""` or api_key containing only whitespace (trim().is_empty()); a skipped/failed env-var substitution leaving the literal empty string.
Common situations: Exporting an empty env var and interpolating it into config; clearing a key by blanking it instead of removing the field; template configs with empty placeholders committed as-is.
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
- external credential consent is scoped to provider
- external credential consent source mismatch for
- Refusing to save an empty API key.
- a CNB access token is not configured in the Codewhale…
- agent profile provider cannot be empty
AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22).
Data as JSON: /api/errors/afc251b557d54aea.
Report an issue: GitHub.
Appendix: source
Thrown at crates/tui/src/config.rs:5110
"Invalid provider '{provider}': expected {}.",
ApiProvider::names_hint()
);
}
let active_provider = self.api_provider();
match validate_kimi_code_api_model_id(
active_provider,
&self.active_route_base_url(),
&self.default_model(),
) {
Err(error) if error == KIMI_CODE_CLAUDE_ALIAS_GUIDANCE => {
return Err(SafeConfigDiagnostic::KimiCodeClaudeAlias.into());
}
result => result.map_err(anyhow::Error::msg)?,
}
if let Some(ref key) = self.api_key
&& key.trim().is_empty()
{
anyhow::bail!("api_key cannot be empty string");
}
if let Some(features) = &self.features {
for key in features.entries.keys() {
if !is_known_feature_key(key) {
anyhow::bail!("Unknown feature flag: {key}");
}
}
}
// Validate the model against the *active provider's* name space, not
// against DeepSeek's. `canonical_model_id_for_provider` is the
// equal-treatment resolver: it applies each family's own canonical map
// (GLM via Z.ai, Kimi, MiniMax, …) and passes unknown ids through, so
// it rejects only what the provider genuinely cannot serve. Validating
// with the DeepSeek-only `normalize_model_name` bricked every config
// whose provider owns a non-DeepSeek family — including ones our own
// setup wizard writes (`provider = "zai"`, `GLM-5.2`). (#4829)
// Provider-scoped choices own the active route. A retained root
// fallback can belong to a different provider after a saved switch.View on GitHub (pinned to 73e0f67d83)