Hmbown/CodeWhale · error · anyhow::Error
Invalid provider '{provider}': expected {}.
Error message
Invalid provider '{provider}': expected {}. What it means
Config validation (crates/tui/src/config.rs:4361) rejects the provider string when ApiProvider::parse cannot recognize it AND no matching custom entry exists under [providers.<name>]. The message embeds the full accepted list from ApiProvider::names_hint() (deepseek, zai, kimi, minimax, moonshot, openai-compatible ids like ollama/vllm/sglang/together, anthropic, google, xai, mistral, ..., custom).
Source
Thrown at crates/tui/src/config.rs:4361
};
tracing::warn!(
"Top-level `base_url = \"{root_base}\"` is ignored for the {provider:?} provider. \
Move it under `[{table}]` (e.g. `[{table}]\\nbase_url = \"...\"`) \
or set the corresponding `*_BASE_URL` env var. (#1308)"
);
}
/// Validate that critical config fields are present.
pub fn validate(&self) -> Result<()> {
if let Some(provider) = self.provider.as_deref()
&& ApiProvider::parse(provider).is_none()
&& self
.providers
.as_ref()
.and_then(|providers| providers.custom_provider_config(provider))
.is_none()
{
anyhow::bail!(
"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()
{View on GitHub (pinned to 8880682c63)
Solutions
- Fix the id to an exact name from the error's expected list (ids are case-sensitive)
- Or define the provider first: a [providers.<name>] section with api_base/model, then set provider = "<name>"
- Check that the custom provider section lives in the same file/profile being loaded
- Rerun and confirm validation passes
Example fix
# config.toml - before provider = "ZAI" # config.toml - after provider = "zai" # or, for a custom endpoint: # [providers.myhost] # api_base = "http://localhost:8000/v1" # provider = "myhost"
Defensive patterns
Strategy: validation
Validate before calling
let ok = ApiProvider::parse(provider).is_some()
|| custom_providers.contains_key(provider);
assert!(ok, "unknown provider {provider}"); Type guard
fn provider_is_known(provider: &str, custom: &HashMap<String, ProviderCfg>) -> bool {
ApiProvider::parse(provider).is_some() || custom.contains_key(provider)
} Try / catch
// Suggest the nearest known id on failure
if !provider_is_known(p, &custom) {
return Err(anyhow!("unknown provider {p}; did you mean '{}'?", closest_name(p)));
} Prevention
- Provider ids are case-sensitive - copy them from names_hint() output
- Define the [providers.<name>] section before referencing it
- Re-check the accepted list after upgrades; ids get added and renamed
When it happens
Trigger: A typo or wrong-case id (ZAI vs zai); using a vendor name that is not a registered id; referencing a custom provider before defining its [providers.<name>] section; profile switching to a provider only defined in another profile.
Common situations: Upgrade adding/renaming provider ids; copying a provider line from another tool's config; defining the custom section under a different key than the one referenced.
Related errors
- {name}.context_window must be greater than 0
- Unknown feature flag: {key}
- Invalid default_text_model '{model}' for provider '{}': expe
- ${name} is required
- ${names.join(" or ")} is required
AI-assisted analysis of Hmbown/CodeWhale@8880682c63 (2026-08-16).
Data as JSON: /api/errors/05026a7d166ed793.
Report an issue: GitHub.