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

ModelProvider returned non-prompt-guided tools payload ({pay

Error message

ModelProvider returned non-prompt-guided tools payload ({payload:?}) while supports_native_tools() is false

What it means

Second step of tool_linker_http(): crate::component::add_wasi_http attaches the wasi:http host functions to the base linker so HttpClient-granted plugins can make network calls. The expect fires when the wasi:http surface cannot be attached — again almost always crate-version mismatch between wasmtime-wasi-http and the engine, or duplicate import definitions on the linker. Cached in a OnceLock, so the first HTTP-capable plugin pays the panic.

Source

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

            .await
    }

    /// Structured chat API for agent loop callers. See `simple_chat` for
    /// the `temperature` contract.
    async fn chat(
        &self,
        request: ChatRequest<'_>,
        model: &str,
        temperature: Option<f64>,
    ) -> anyhow::Result<ChatResponse> {
        if let Some(tools) = request.tools
            && !tools.is_empty()
            && !self.supports_native_tools()
        {
            let tool_instructions = match self.convert_tools(tools) {
                ToolsPayload::PromptGuided { instructions } => instructions,
                payload => {
                    anyhow::bail!(
                        "ModelProvider returned non-prompt-guided tools payload ({payload:?}) while supports_native_tools() is false"
                    )
                }
            };
            let mut modified_messages = request.messages.to_vec();

            if let Some(system_message) = modified_messages.iter_mut().find(|m| m.role == "system")
            {
                if !system_message.content.is_empty() {
                    system_message.content.push_str("\n\n");
                }
                system_message.content.push_str(&tool_instructions);
            } else {
                modified_messages.insert(0, ChatMessage::system(tool_instructions));
            }

            let text = self
                .chat_with_history(&modified_messages, model, temperature)

View on GitHub (pinned to 88bb9c8533)

Solutions

  1. Update wasmtime-wasi-http to the same version as wasmtime and wasmtime-wasi, then cargo clean and rebuild.
  2. Inspect Cargo.lock for mixed wasmtime* versions (`grep -A1 'name = "wasmtime' Cargo.lock`).
  3. Regenerate wasi:http bindings from the same wit snapshot the host crates target.
  4. Add a create_plugin smoke test for an HTTP-granted plugin to CI so this fails at build time, not in production.

Example fix

// before
crate::component::add_wasi_http(&mut linker).expect("tool http linker");

// after
crate::component::add_wasi_http(&mut linker)
    .map_err(|e| anyhow::anyhow!("wasi:http linker init failed: {e:#}"))?;
Defensive patterns

Strategy: fallback

Try / catch

// Contain the wasi:http linker panic at the plugin host boundary:
let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
    futures::executor::block_on(create_plugin(&wasm_path, &http_scope, limits))
}));
match result {
    Err(_) => { /* disable HttpClient capability, run plugin without network */ }
    Ok(_) => { /* proceed */ }
}

Prevention

When it happens

Trigger: First instantiation of an HttpClient-capable plugin with wasmtime-wasi-http version skew, or a fork that already defined wasi:http imports on the linker before add_wasi_http runs.

Common situations: wasmtime-wasi-http left on an old version while wasmtime was upgraded; vendored bindings out of sync with the host crates; plugins rebuilt with a newer wit toolchain than the host supports.

Related errors


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