{"record":{"id":"afc251b557d54aea","repo":"Hmbown/CodeWhale","slug":"api-key-cannot-be-empty-string","errorCode":null,"errorMessage":"api_key cannot be empty string","messagePattern":"api_key cannot be empty string","errorType":"validation","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/tui/src/config.rs","lineNumber":4380,"sourceCode":"                \"Invalid provider '{provider}': expected {}.\",\n                ApiProvider::names_hint()\n            );\n        }\n        let active_provider = self.api_provider();\n        match validate_kimi_code_api_model_id(\n            active_provider,\n            &self.deepseek_base_url(),\n            &self.default_model(),\n        ) {\n            Err(error) if error == KIMI_CODE_CLAUDE_ALIAS_GUIDANCE => {\n                return Err(SafeConfigDiagnostic::KimiCodeClaudeAlias.into());\n            }\n            result => result.map_err(anyhow::Error::msg)?,\n        }\n        if let Some(ref key) = self.api_key\n            && key.trim().is_empty()\n        {\n            anyhow::bail!(\"api_key cannot be empty string\");\n        }\n        if let Some(features) = &self.features {\n            for key in features.entries.keys() {\n                if !is_known_feature_key(key) {\n                    anyhow::bail!(\"Unknown feature flag: {key}\");\n                }\n            }\n        }\n        // Validate the model against the *active provider's* name space, not\n        // against DeepSeek's. `canonical_model_id_for_provider` is the\n        // equal-treatment resolver: it applies each family's own canonical map\n        // (GLM via Z.ai, Kimi, MiniMax, …) and passes unknown ids through, so\n        // it rejects only what the provider genuinely cannot serve. Validating\n        // with the DeepSeek-only `normalize_model_name` bricked every config\n        // whose provider owns a non-DeepSeek family — including ones our own\n        // setup wizard writes (`provider = \"zai\"`, `GLM-5.2`). (#4829)\n        if let Some(model) = self.default_text_model.as_deref()\n            && !model.trim().eq_ignore_ascii_case(\"auto\")","sourceCodeStart":4362,"sourceCodeEnd":4398,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/8880682c63083a91624de936797efa3ce9e498fd/crates/tui/src/config.rs#L4362-L4398","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"# config.toml - before\napi_key = \"\"\n\n# config.toml - after\n# option 1: remove the line and export the env var instead\n# option 2: real value\napi_key = \"sk-...\"\n","handlingStrategy":"validation","validationCode":"if let Some(k) = &cfg.api_key {\n    ensure!(!k.trim().is_empty(), \"api_key is blank - remove the line or set a real value\");\n}","typeGuard":"fn api_key_usable(cfg: &Config) -> bool {\n    cfg.api_key.as_deref().is_none_or(|k| !k.trim().is_empty())\n}","tryCatchPattern":"// In secret-scrubbing pipelines: delete the key, never blank it\nscrubbed.api_key = None; // not Some(String::new())","preventionTips":["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"],"tags":["config","api-key","secrets","validation"],"backgroundTag":null,"analyzedSha":"8880682c63083a91624de936797efa3ce9e498fd","analyzedAt":"2026-08-16T11:31:27.956Z","schemaVersion":2},"datasetVersion":"2026-08-16T13:17:31.715Z"}