{"record":{"id":"a910be1ac1bb868c","repo":"Hmbown/CodeWhale","slug":"custom-provider-id-cannot-be-empty","errorCode":null,"errorMessage":"custom provider id cannot be empty","messagePattern":"custom provider id cannot be empty","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/tui/src/config.rs","lineNumber":12525,"sourceCode":"        && identity.key.trim() == ApiProvider::Custom.as_str()\n        && identity.persisted_id().is_none();\n    if matches!(provider, ApiProvider::Deepseek | ApiProvider::DeepseekCN) {\n        return save_api_key(api_key);\n    }\n    if is_legacy_literal_custom {\n        return save_root_api_key_for_secret_slot(api_key, \"custom\", false);\n    }\n\n    let api_key = api_key.trim();\n    anyhow::ensure!(!api_key.is_empty(), \"Refusing to save an empty API key.\");\n\n    let config_path =\n        credential_config_path().context(\"Failed to resolve config path for provider API key.\")?;\n    ensure_parent_dir(&config_path)?;\n\n    let key_inside = if provider == ApiProvider::Custom {\n        let key = identity.key.trim();\n        anyhow::ensure!(!key.is_empty(), \"custom provider id cannot be empty\");\n        key\n    } else {\n        provider_config_key(provider).context(\"provider api key table\")?\n    };\n    // A legacy, manually-selected Kimi CLI import implicitly routed Moonshot\n    // traffic to Kimi Code. Once the user replaces that import with the\n    // supported API-key route, persist the endpoint before changing auth_mode\n    // so the key is not silently sent to the ordinary Moonshot endpoint.\n    // Respect an explicit user-owned endpoint.\n    let pin_kimi_code_base_url = provider == ApiProvider::Moonshot\n        && route_config\n            .provider_config_for(provider)\n            .is_some_and(|entry| {\n                provider_config_uses_kimi_imported_token(entry)\n                    && entry\n                        .base_url\n                        .as_deref()\n                        .is_none_or(|base_url| base_url.trim().is_empty())","sourceCodeStart":12507,"sourceCodeEnd":12543,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/73e0f67d83c59909b571efdfc88c4bc28c309cb1/crates/tui/src/config.rs#L12507-L12543","documentation":"For the `Custom` provider, the API key is stored under a per-custom-provider id (`identity.key`); this `ensure!` guard rejects an empty id because there is no table key to write the secret under. Non-custom providers use `provider_config_key` instead and never hit this.","triggerScenarios":"Saving an API key with `provider == ApiProvider::Custom` and an `identity.key` that is empty or whitespace-only after trim.","commonSituations":"A custom provider was constructed programmatically without an id; form state lost the custom-provider selection/key pairing; deserialized identity with a blank key field.","solutions":["Supply a non-empty `identity.key` for the custom provider before saving","Validate custom-provider creation so an id is always assigned","Use a named custom provider (select/created with an id) rather than a bare `Custom` identity"],"exampleFix":null,"handlingStrategy":"validation","validationCode":"if provider == ApiProvider::Custom && identity.key.trim().is_empty() {\n    return Err(anyhow::anyhow!(\"custom provider id required\"));\n}","typeGuard":null,"tryCatchPattern":"if let Err(e) = save_api_key_for_identity(&identity, key) {\n    if e.to_string().contains(\"custom provider id cannot be empty\") {\n        // re-select or recreate the custom provider with an id\n    }\n}","preventionTips":["Assign an id at custom-provider creation time and never clear it","Validate identity.key before any per-identity save operation","Keep custom-provider selection and its id in the same form state"],"tags":["validation","custom-provider","empty-input"],"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"}