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

ACP request_permission timed out after {timeout:?}

Error message

ACP request_permission timed out after {timeout:?}

What it means

The companion check in AzureOpenAiBuilder::build(): after resource_name, it reads deployment_name with this expect. The deployment name is the second half of the required URL pair — without it the https://{resource}.openai.azure.com/openai/deployments/{deployment} endpoint cannot be formed, which is why the builder documents the panic. It fires when deployment_name() was never called on the builder.

Source

Thrown at crates/zeroclaw-channels/src/acp_channel.rs:97

            "options": options,
            // `toolCall` is required by the ACP schema. We use a synthetic
            // ask_user tool call so the client surfaces the prompt with a
            // sensible title.
            "toolCall": {
                "toolCallId": format!("ask-user-{}", uuid::Uuid::new_v4()),
                "title": question,
                "kind": "other",
                "status": "pending",
            }
        });

        let call = self.rpc.request("session/request_permission", params);
        let response = match tokio::time::timeout(timeout, call).await {
            Ok(Ok(value)) => value,
            Ok(Err(e)) => {
                anyhow::bail!("ACP request_permission failed: {} ({})", e.message, e.code)
            }
            Err(_) => anyhow::bail!("ACP request_permission timed out after {timeout:?}"),
        };

        // Response shape: { outcome: { outcome: "selected", optionId: "..." } | { outcome: "cancelled" } }
        let outcome = response.get("outcome");
        let kind = outcome
            .and_then(|o| o.get("outcome"))
            .and_then(|s| s.as_str())
            .unwrap_or("");
        match kind {
            "selected" => {
                let option_id = outcome
                    .and_then(|o| o.get("optionId"))
                    .and_then(|s| s.as_str())
                    .unwrap_or("");
                let idx = option_id
                    .strip_prefix("choice-")
                    .and_then(|s| s.parse::<usize>().ok());
                match idx.and_then(|i| choices.get(i)) {

View on GitHub (pinned to 88bb9c8533)

Solutions

  1. Set deployment_name() to the Azure deployment id (not the model name) before build().
  2. Validate both fields together when parsing provider config, failing with one actionable message listing both keys.
  3. Prefer a constructor taking resource and deployment as parameters so the type system enforces presence.
  4. Keep the listed builder tests green as a regression net.

Example fix

// before
let provider = AzureOpenAiModelProvider::builder()
    .resource_name("my-resource")
    .build(); // panics: deployment_name() is required

// after
let provider = AzureOpenAiModelProvider::builder()
    .resource_name("my-resource")
    .deployment_name("gpt-4o-deploy")
    .build();
Defensive patterns

Strategy: validation

Validate before calling

// Check both halves of the URL pair before build():
fn azure_url_inputs_present(cfg: &AzureProviderConfig) -> Result<(), String> {
    match (&cfg.resource_name, &cfg.deployment_name) {
        (Some(r), Some(d)) if !r.is_empty() && !d.is_empty() => Ok(()),
        _ => Err("azure provider needs both resource_name and deployment_name".into()),
    }
}

Type guard

fn azure_deployment_set(cfg: &serde_json::Value) -> bool {
    cfg.get("deployment_name")
        .and_then(|v| v.as_str())
        .map_or(false, |s| !s.trim().is_empty())
}

Try / catch

let p = std::panic::catch_unwind(|| {
    AzureOpenAiModelProvider::builder()
        .resource_name(r.clone())
        .deployment_name(d.clone())
        .build()
});
if p.is_err() { /* surface a config error naming resource_name and deployment_name */ }

Prevention

When it happens

Trigger: build() invoked after setting resource_name but not deployment_name — e.g. config supplied the Azure resource yet omitted the deployment/model deployment id.

Common situations: Azure provider config that names the resource but not the deployment; deployments renamed in Azure portal so old config keys no longer map; test or tooling code constructing the builder with only one of the two required setters.

Understand the failure class

Related errors


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