Hmbown/CodeWhale · error · anyhow::Error
api_key cannot be empty string
Error message
api_key cannot be empty string
What it means
Config validation (crates/tui/src/config.rs:4380) rejects api_key when it is present but only whitespace. An empty-string key is almost always a redacted file or a broken template substitution; treating it as 'no key' would produce confusing 401s later, so load fails fast.
Source
Thrown at crates/tui/src/config.rs:4380
"Invalid provider '{provider}': expected {}.",
ApiProvider::names_hint()
);
}
let active_provider = self.api_provider();
match validate_kimi_code_api_model_id(
active_provider,
&self.deepseek_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)
if let Some(model) = self.default_text_model.as_deref()
&& !model.trim().eq_ignore_ascii_case("auto")View on GitHub (pinned to 8880682c63)
Solutions
- Set the real key value in the config, or delete the api_key line entirely so env-var auth applies
- If a template produced the empty value, fix the substitution and regenerate
- Never commit the filled-in file; use the environment or a secret manager
Example fix
# config.toml - before api_key = "" # config.toml - after # option 1: remove the line and export the env var instead # option 2: real value api_key = "sk-..."
Defensive patterns
Strategy: validation
Validate before calling
if let Some(k) = &cfg.api_key {
ensure!(!k.trim().is_empty(), "api_key is blank - remove the line or set a real value");
} Type guard
fn api_key_usable(cfg: &Config) -> bool {
cfg.api_key.as_deref().is_none_or(|k| !k.trim().is_empty())
} Try / catch
// In secret-scrubbing pipelines: delete the key, never blank it scrubbed.api_key = None; // not Some(String::new())
Prevention
- Blank strings are rejected on purpose - absent means env-var auth
- Run secret redaction that removes keys rather than emptying them
- After templating configs, grep for 'api_key = ""' before deploy
When it happens
Trigger: Secret-redaction tooling blanking the value in a shared config; envsubst/template pipelines that leave the literal empty; committing a config with the key removed by leaving quotes.
Common situations: Dotfile repos where keys are scrubbed before commit; CI injecting keys via templates that failed silently; switching from key-in-config to env-var auth halfway.
Related errors
- Secret storage write failed for {slot}: {err}. Refusing to w
- ${name} is required
- ${names.join(" or ")} is required
- ${name} is required
- CF_ACCOUNT_ID and CF_API_TOKEN are required
AI-assisted analysis of Hmbown/CodeWhale@8880682c63 (2026-08-16).
Data as JSON: /api/errors/afc251b557d54aea.
Report an issue: GitHub.