zeroclaw-labs/zeroclaw · error · anyhow::Error

agents.{agent_alias}.model_provider resolves to a model_prov

Error message

agents.{agent_alias}.model_provider resolves to a model_provider entry with no `model` set. Configure [providers.models.{provider_name}.<alias>] model = "...".

What it means

On the process_message path, the agent's model_provider resolved to an entry whose model field is unset or blank after trimming, so no model name could be determined for the turn. Same guard as agent construction (agent.rs), re-enforced at message time to catch post-startup config drift.

Source

Thrown at crates/zeroclaw-runtime/src/agent/loop_.rs:3049

        );
        if count > 0 {
            ::zeroclaw_log::record!(
                INFO,
                ::zeroclaw_log::Event::new(module_path!(), ::zeroclaw_log::Action::Register)
                    .with_category(::zeroclaw_log::EventCategory::Channel)
                    .with_attrs(::serde_json::json!({"count": count})),
                &format!("Registered {} channel(s) for process_message agent", count),
            );
        }

        let model_name = match agent_model_provider
            .as_ref()
            .and_then(|e| e.model.as_deref())
            .map(str::trim)
            .filter(|m| !m.is_empty())
        {
            Some(m) => m.to_string(),
            None => anyhow::bail!(
                "agents.{agent_alias}.model_provider resolves to a model_provider entry with no \
             `model` set. Configure [providers.models.{provider_name}.<alias>] model = \"...\"."
            ),
        };
        let provider_runtime_options = zeroclaw_providers::provider_runtime_options_for_alias(
            &config,
            provider_name,
            provider_alias.as_str(),
        );
        let model_provider: Box<dyn ModelProvider> =
            zeroclaw_providers::create_routed_model_provider_with_options(
                &config,
                &format!("{provider_name}.{provider_alias}"),
                agent_model_provider
                    .as_ref()
                    .and_then(|e| e.api_key.as_deref()),
                agent_model_provider.as_ref().and_then(|e| e.uri.as_deref()),
                &config.reliability,

View on GitHub (pinned to 88bb9c8533)

Solutions

  1. Set model = "<model-id>" in the named [providers.models.<type>.<alias>] table.
  2. Restart/reload the runtime so the fixed config takes effect.
  3. Add a startup check that every resolved entry has a non-empty model.

Example fix

# before
[providers.models.openai.main]
api_key_env = "OPENAI_API_KEY"
# model key absent

# after
[providers.models.openai.main]
api_key_env = "OPENAI_API_KEY"
model = "gpt-4o"
Defensive patterns

Strategy: validation

Validate before calling

// Before dispatch: resolved entry must name a model
if let Some((_p, _a, entry)) = config.resolved_model_provider_for_agent(alias) {
    anyhow::ensure!(
        entry.model.as_deref().map(str::trim).filter(|m| !m.is_empty()).is_some(),
        "agent {alias}: provider entry has no model"
    );
}

Prevention

When it happens

Trigger: A daemon-driven turn uses an agent whose provider table lost its model key after a reload; an entry configured with auth but model left for later; whitespace-only model value.

Common situations: Partial config deploys; renaming the model key or commenting it out; multi-layer configs where the layer with the model key is not loaded.

Related errors


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