{"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":null,"httpStatus":null,"severity":"error","filePath":"crates/tui/src/config.rs","lineNumber":5110,"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.active_route_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        // Provider-scoped choices own the active route. A retained root\n        // fallback can belong to a different provider after a saved switch.","sourceCodeStart":5092,"sourceCodeEnd":5128,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/73e0f67d83c59909b571efdfc88c4bc28c309cb1/crates/tui/src/config.rs#L5092-L5128","documentation":"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.","triggerScenarios":"Config has `api_key = \"\"` or api_key containing only whitespace (trim().is_empty()); a skipped/failed env-var substitution leaving the literal empty string.","commonSituations":"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.","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 = \"\"`."],"exampleFix":"// before\napi_key = \"\"\n// after\n# api_key removed; key read from environment","handlingStrategy":"validation","validationCode":"if let Some(k) = &config.api_key {\n    if k.trim().is_empty() { return Err(\"api_key must be non-empty or omitted\"); }\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Remove blank api_key fields instead of empty-string placeholders.","Fail fast in scripts when the credential env var is empty before interpolation."],"tags":["config","api-key","credentials","validation"],"backgroundTag":"empty-required-field","analyzedSha":"73e0f67d83c59909b571efdfc88c4bc28c309cb1","analyzedAt":"2026-09-22T01:30:00.501Z","contentChangedAt":"2026-09-22T01:30:00.501Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}