zeroclaw-labs/zeroclaw · error · anyhow::Error
ACP request_permission failed: {} ({})
Error message
ACP request_permission failed: {} ({}) What it means
AzureOpenAiBuilder::build() constructs the Azure OpenAI provider and, per its doc comment, requires both resource_name() and deployment_name() because the deployment URL https://{resource}.openai.azure.com/openai/deployments/{deployment} has no sensible default for either. This expect fires when build() runs without resource_name having been set. The tests listed under RAISED IN are the canonical callers that always set both before building.
Source
Thrown at crates/zeroclaw-channels/src/acp_channel.rs:95
let params = json!({
"sessionId": self.session_id,
"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-")View on GitHub (pinned to 88bb9c8533)
Solutions
- Set resource_name() (the <resource> prefix of <resource>.openai.azure.com) before build().
- Validate config at load time: reject azure provider entries missing resource or deployment with a clear error before the builder runs.
- Restructure to make missing values unrepresentable: a new(resource, deployment) constructor, keeping Option setters only for genuinely optional fields like api_version.
- After touching this code, run the azure_openai builder tests (url_construction_*, auth_header_*, creates_*).
Example fix
// before
let provider = AzureOpenAiModelProvider::builder()
.deployment_name("gpt-4o")
.build(); // panics: resource_name() is required
// after
let provider = AzureOpenAiModelProvider::builder()
.resource_name("my-resource")
.deployment_name("gpt-4o")
.build(); Defensive patterns
Strategy: validation
Validate before calling
// Validate config before touching the builder:
fn azure_entry_ok(resource: &Option<String>, deployment: &Option<String>) -> Result<(), String> {
if resource.as_deref().unwrap_or("").is_empty() {
return Err("azure provider: resource_name missing".into());
}
if deployment.as_deref().unwrap_or("").is_empty() {
return Err("azure provider: deployment_name missing".into());
}
Ok(())
} Type guard
// Builder fields are private, so guard at the config layer:
fn has_azure_required(cfg: &serde_json::Value) -> bool {
cfg.get("resource_name").and_then(|v| v.as_str()).map_or(false, |s| !s.is_empty())
&& cfg.get("deployment_name").and_then(|v| v.as_str()).map_or(false, |s| !s.is_empty())
} Try / catch
// Builders panic rather than return Result; contain it if you must:
let p = std::panic::catch_unwind(|| {
AzureOpenAiModelProvider::builder()
.resource_name(r.clone())
.deployment_name(d.clone())
.build()
});
match p {
Ok(provider) => { /* use */ }
Err(_) => { /* report config error with both required keys */ }
} Prevention
- Map every required config key explicitly and fail at load time with the key names
- Prefer constructors that take required parameters over Option setters
- Keep the azure builder unit tests (url_construction_*, auth_header_*, creates_*) green
- Add a config schema check for azure providers in CI
When it happens
Trigger: Calling build() on a builder whose resource_name() setter was skipped — typically provider config parsing that maps an Azure entry missing its resource field straight onto the builder.
Common situations: A config file with an azure provider entry missing the resource name key; a rename of the config field so the mapping silently drops it; a new code path constructing the builder without porting all required setters.
Related errors
- ACP request_permission timed out after {timeout:?}
- ACP returned unknown optionId: {option_id}
- purge_agent not supported by this memory backend
- rename_agent not supported by this memory backend
- memory backend '{}' does not support StoreOptions kind/pinne
AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23).
Data as JSON: /api/errors/e100db6ebf454b82.
Report an issue: GitHub.