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

live model listing is not supported for this model_provider

Error message

live model listing is not supported for this model_provider

What it means

tool_linker_http() is the HttpClient-granted linker variant, built lazily on the first plugin whose PluginInstanceScope grants HttpClient. Its first step builds the base linker (WASI plus tool bindings) with the same expect("tool linker") used by the non-HTTP path. It fails for the same version/generation-skew reasons, but only HTTP-granted plugins trigger it — non-HTTP plugins keep working, which can mask the scope of the breakage.

Source

Thrown at crates/zeroclaw-api/src/model_provider.rs:597

        model: &str,
        temperature: Option<f64>,
    ) -> anyhow::Result<String> {
        self.chat_with_system(None, message, model, temperature)
            .await
    }

    /// One-shot chat with optional system prompt. See `simple_chat` for
    /// the `temperature` contract.
    async fn chat_with_system(
        &self,
        system_prompt: Option<&str>,
        message: &str,
        model: &str,
        temperature: Option<f64>,
    ) -> anyhow::Result<String>;

    async fn list_models(&self) -> anyhow::Result<Vec<String>> {
        anyhow::bail!("live model listing is not supported for this model_provider")
    }

    /// Fetch the list of available models with pricing data for this
    /// model_provider. Default delegates to `list_models` and returns no
    /// pricing. Concrete providers that receive pricing from their `/models`
    /// endpoint override this to return enriched data.
    async fn list_models_with_pricing(&self) -> anyhow::Result<Vec<ModelInfo>> {
        Ok(self
            .list_models()
            .await?
            .into_iter()
            .map(|id| ModelInfo {
                id,
                pricing: None,
                context_window: None,
            })
            .collect())
    }

View on GitHub (pinned to 88bb9c8533)

Solutions

  1. Align wasmtime, wasmtime-wasi, and wasmtime-wasi-http on one version and rebuild.
  2. Cover both linker paths with a startup smoke test that instantiates one HTTP-granted and one plain plugin.
  3. Regenerate bindings from wit/v0 consistently for base and HTTP worlds.
  4. If embedding, pre-create one HTTP plugin at startup so this surfaces as a boot failure with full context.

Example fix

// before
LINKER.get_or_init(|| {
    let mut linker = base_linker().expect("tool linker");
    crate::component::add_wasi_http(&mut linker).expect("tool http linker");
    linker
})

// after — propagate instead of panicking
static LINKER: OnceLock<anyhow::Result<Linker<PluginState>>> = OnceLock::new();
LINKER.get_or_init(|| {
    let mut linker = base_linker()?;
    crate::component::add_wasi_http(&mut linker)
        .map_err(|e| anyhow::anyhow!("wasi:http linker init failed: {e:#}"))?;
    Ok(linker)
});
Defensive patterns

Strategy: fallback

Try / catch

// Probe an HTTP-granted plugin at startup, not at first user request:
let probe = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
    futures::executor::block_on(create_plugin(&http_plugin_path, &http_scope, limits))
}));
if probe.is_err() {
    tracing::error!("HTTP plugin linker broken; disabling HttpClient plugins");
}

Prevention

When it happens

Trigger: The first create_plugin() for a plugin with the HttpClient capability when wasmtime or the generated tool bindings are version-skewed; the OnceLock caches the panic-inducing initialization attempt context but the panic itself fires at first use.

Common situations: Mixed wasmtime versions after a partial dependency upgrade; a fork that regenerates bindings for the base world but not the HTTP world; CI passing non-HTTP plugin tests while HTTP plugins panic at runtime.

Related errors


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