Hmbown/CodeWhale · error · anyhow::Error

Invalid default_text_model '{model}' for provider '{}': expe

Error message

Invalid default_text_model '{model}' for provider '{}': expected auto or a model ID this provider serves{hint}.

What it means

Thrown by Config::validate() (crates/tui/src/config.rs:4352) when default_text_model is neither 'auto' (case-insensitive, trimmed) nor a model ID the active provider serves. The check is skipped for providers that pass model IDs straight through or when a custom base URL preserves the model. When the provider has a known catalog, the message appends example model names via model_completion_names_for_provider().

Source

Thrown at crates/tui/src/config.rs:4410

        // (GLM via Z.ai, Kimi, MiniMax, …) and passes unknown ids through, so
        // it rejects only what the provider genuinely cannot serve. Validating
        // with the DeepSeek-only `normalize_model_name` bricked every config
        // whose provider owns a non-DeepSeek family — including ones our own
        // setup wizard writes (`provider = "zai"`, `GLM-5.2`). (#4829)
        if let Some(model) = self.default_text_model.as_deref()
            && !model.trim().eq_ignore_ascii_case("auto")
            && !provider_passes_model_through(self.api_provider())
            && !self.active_provider_preserves_custom_base_url_model()
            && canonical_model_id_for_provider(self.api_provider(), model).is_none()
        {
            let provider = self.api_provider();
            let known = model_completion_names_for_provider(provider);
            let hint = if known.is_empty() {
                String::new()
            } else {
                format!(" (for example: {})", known.join(", "))
            };
            anyhow::bail!(
                "Invalid default_text_model '{model}' for provider '{}': expected auto or a model ID this provider serves{hint}.",
                provider.as_str()
            );
        }
        if let Some(policy) = self.approval_policy.as_deref() {
            let normalized = policy.trim().to_ascii_lowercase();
            if !matches!(
                normalized.as_str(),
                "on-request" | "untrusted" | "never" | "auto" | "suggest"
            ) {
                anyhow::bail!(
                    "Invalid approval_policy '{policy}': expected on-request, untrusted, never, auto, or suggest."
                );
            }
        }
        if let Some(v) = self.verbosity.as_deref() {
            let normalized = v.trim().to_ascii_lowercase();
            if !matches!(normalized.as_str(), "normal" | "concise") {

View on GitHub (pinned to 8880682c63)

Solutions

  1. Set default_text_model = "auto" and let the provider pick its default.
  2. Replace the value with a model ID this provider serves (the message hint lists examples, e.g. deepseek-chat).
  3. If the endpoint serves arbitrary model names, route through a custom provider entry or custom base_url so the passthrough/custom-base-url exemptions apply.
  4. Re-run validation after editing to confirm the config loads.

Example fix

# before (~/.codewhale/config.toml)
provider = "deepseek"
default_text_model = "gpt-4o"

# after
provider = "deepseek"
default_text_model = "auto"   # or "deepseek-chat" / "deepseek-reasoner"
Defensive patterns

Strategy: validation

Validate before calling

fn default_text_model_is_safe(provider: ApiProvider, model: &str) -> bool {
    let m = model.trim();
    m.eq_ignore_ascii_case("auto")
        || canonical_model_id_for_provider(provider, m).is_some()
        // passthrough/custom-base-url routes exempt from the catalog check
        || provider_passes_model_through(provider)
}

// run before startup:
let model = config.default_text_model.as_deref().unwrap_or("auto");
assert!(default_text_model_is_safe(config.api_provider(), model), "fix default_text_model");

Type guard

fn is_valid_default_text_model(provider: ApiProvider, model: &str) -> bool {
    matches!(model.trim(), "auto" if model.trim().eq_ignore_ascii_case("auto"))
        || canonical_model_id_for_provider(provider, model.trim()).is_some()
}

Try / catch

match config.validate() {
    Ok(()) => {}
    Err(e) if e.to_string().starts_with("Invalid default_text_model") => {
        // normalize to "auto" and retry validation before surfacing to the user
        config.default_text_model = Some("auto".into());
        config.validate()?;
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: Setting default_text_model to another vendor's model ID (e.g. 'gpt-4o' while provider = 'deepseek'); switching the active provider without updating a stale model name; a typo such as 'deepseek-chatt'; canonical_model_id_for_provider() returning None for the pair.

Common situations: Copy-pasting config snippets written for a different provider; flipping providers with --provider while a shared ~/.codewhale/config.toml pins the old model; model catalog renames after an upgrade.

Related errors


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