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

  1. Fix the id to an exact name from the error's expected list (ids are case-sensitive)
  2. Or define the provider first: a [providers.<name>] section with api_base/model, then set provider = "<name>"
  3. Check that the custom provider section lives in the same file/profile being loaded
  4. 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

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


AI-assisted analysis of Hmbown/CodeWhale@8880682c63 (2026-08-16). Data as JSON: /api/errors/05026a7d166ed793. Report an issue: GitHub.