zeroclaw-labs/zeroclaw · warning

ModelProvider name cannot be empty

Error message

ModelProvider name cannot be empty

What it means

AuthProvider::from_str rejects input that is empty after trim + to_ascii_lowercase. from_str first normalizes, then bails on the empty string before attempting serde deserialization of the canonical provider name (openai-codex, anthropic, gemini, xai and their aliases). A non-empty but unknown name takes a different path (serde error with a WARN log), so this specific error means the caller passed "" or whitespace only.

Source

Thrown at crates/zeroclaw-providers/src/auth/mod.rs:614

#[serde(rename_all = "kebab-case")]
pub enum AuthProvider {
    #[serde(alias = "openai_codex", alias = "codex")]
    OpenaiCodex,
    #[serde(alias = "claude")]
    Anthropic,
    #[serde(alias = "google", alias = "vertex")]
    Gemini,
    #[serde(alias = "grok")]
    Xai,
}

impl std::str::FromStr for AuthProvider {
    type Err = anyhow::Error;

    fn from_str(raw: &str) -> Result<Self> {
        let normalized = raw.trim().to_ascii_lowercase();
        if normalized.is_empty() {
            anyhow::bail!("ModelProvider name cannot be empty");
        }
        serde_json::from_value(serde_json::Value::String(normalized.clone())).map_err(|_| {
            ::zeroclaw_log::record!(
                WARN,
                ::zeroclaw_log::Event::new(module_path!(), ::zeroclaw_log::Action::Reject)
                    .with_outcome(::zeroclaw_log::EventOutcome::Failure)
                    .with_attrs(::serde_json::json!({"normalized": &normalized})),
                "auth: unknown auth provider"
            );
            anyhow::Error::msg(format!(
                "Unknown auth provider `{normalized}`. Supported: openai-codex, anthropic, gemini, xai.",
            ))
        })
    }
}

impl AuthProvider {
    /// Canonical lowercase name for storage, profile lookup, and on-the-wire

View on GitHub (pinned to 88bb9c8533)

Solutions

  1. Set a real provider name: openai-codex (alias codex), anthropic (alias claude), gemini (alias google/vertex), or xai (alias grok)
  2. Trace where the empty string comes from — usually an unset config key or environment variable feeding the parse call
  3. Validate provider names early at config load with normalize_model_provider instead of deep inside auth flows

Example fix

// before: parsing an optional config value directly
let provider: AuthProvider = cfg.get("model_provider").unwrap_or("").parse()?;

// after: fail fast on the missing key with a clear message
let raw = cfg.get("model_provider").ok_or_else(|| anyhow::anyhow!("model_provider not set"))?;
let provider: AuthProvider = raw.parse()?;
Defensive patterns

Strategy: validation

Validate before calling

fn provider_name_is_parseable(raw: &str) -> bool {
    !raw.trim().is_empty()
}

assert!(provider_name_is_parseable(&raw));
let provider: AuthProvider = raw.parse()?;

Type guard

fn is_non_empty_provider_name(raw: &str) -> bool {
    !raw.trim().is_empty()
}

Try / catch

match raw.parse::<AuthProvider>() {
    Ok(p) => Ok(p),
    Err(e) if e.to_string().contains("cannot be empty") => {
        Err(anyhow::anyhow!("model_provider is not configured"))
    }
    Err(e) => Err(e),
}

Prevention

When it happens

Trigger: Parsing a model-provider string built from an unset config key or env var (e.g. format!("{}", missing) yielding ""), a CLI --model-provider flag left blank, or config.toml with model_provider = "".

Common situations: A template/config generator that leaves model_provider empty by default, an env var name typo so the value resolves to an empty string, or string interpolation of an Option that renders as empty text.

Related errors


AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23). Data as JSON: /api/errors/6c41d0d3bcae8289. Report an issue: GitHub.