Hmbown/CodeWhale · error
custom provider id cannot be empty
Error message
custom provider id cannot be empty
What it means
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.
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
Defensive patterns
Strategy: validation
Validate before calling
if provider == ApiProvider::Custom && identity.key.trim().is_empty() {
return Err(anyhow::anyhow!("custom provider id required"));
} Try / catch
if let Err(e) = save_api_key_for_identity(&identity, key) {
if e.to_string().contains("custom provider id cannot be empty") {
// re-select or recreate the custom provider with an id
}
} Prevention
- 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
When it happens
Trigger: Saving an API key with `provider == ApiProvider::Custom` and an `identity.key` that is empty or whitespace-only after trim.
Common situations: 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.
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
- base URL cannot be empty
- browser URL cannot be empty
- classifier-approved read command was empty
- Codewhale runtime event file is empty.
- CRON field must not be empty
AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22).
Data as JSON: /api/errors/a910be1ac1bb868c.
Report an issue: GitHub.
Appendix: source
Thrown at crates/tui/src/config.rs:12525
&& identity.key.trim() == ApiProvider::Custom.as_str()
&& identity.persisted_id().is_none();
if matches!(provider, ApiProvider::Deepseek | ApiProvider::DeepseekCN) {
return save_api_key(api_key);
}
if is_legacy_literal_custom {
return save_root_api_key_for_secret_slot(api_key, "custom", false);
}
let api_key = api_key.trim();
anyhow::ensure!(!api_key.is_empty(), "Refusing to save an empty API key.");
let config_path =
credential_config_path().context("Failed to resolve config path for provider API key.")?;
ensure_parent_dir(&config_path)?;
let key_inside = if provider == ApiProvider::Custom {
let key = identity.key.trim();
anyhow::ensure!(!key.is_empty(), "custom provider id cannot be empty");
key
} else {
provider_config_key(provider).context("provider api key table")?
};
// A legacy, manually-selected Kimi CLI import implicitly routed Moonshot
// traffic to Kimi Code. Once the user replaces that import with the
// supported API-key route, persist the endpoint before changing auth_mode
// so the key is not silently sent to the ordinary Moonshot endpoint.
// Respect an explicit user-owned endpoint.
let pin_kimi_code_base_url = provider == ApiProvider::Moonshot
&& route_config
.provider_config_for(provider)
.is_some_and(|entry| {
provider_config_uses_kimi_imported_token(entry)
&& entry
.base_url
.as_deref()
.is_none_or(|base_url| base_url.trim().is_empty())View on GitHub (pinned to 73e0f67d83)