zeroclaw-labs/zeroclaw · error · anyhow::Error
ACP returned unexpected permission outcome: {other}
Error message
ACP returned unexpected permission outcome: {other} What it means
The approval response's outcome.outcome was neither "selected" nor "cancelled" — the only two outcomes request_approval_attributed understands. As with the choice path, a response missing the nested outcome field parses as an empty string and lands here too. It signals a malformed or version-drifted client response, not an operator decision.
Source
Thrown at crates/zeroclaw-channels/src/acp_channel.rs:542
.unwrap_or("")
.to_string();
ChannelApprovalResponse::DenyWithEdit { replacement }
}
other => anyhow::bail!("ACP returned unknown permission optionId: {other}"),
};
Ok(Some(
zeroclaw_api::channel::AttributedApprovalResponse::operator(response),
))
}
// The client withdrew the prompt; nobody chose anything. Still a
// deny, but the runtime's, not the operator's.
"cancelled" => Ok(Some(
zeroclaw_api::channel::AttributedApprovalResponse::from_runtime(
ChannelApprovalResponse::Deny,
zeroclaw_api::channel::ApprovalSource::Unreachable,
),
)),
other => anyhow::bail!("ACP returned unexpected permission outcome: {other}"),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use tokio::sync::mpsc;
use zeroclaw_api::elicitation::single_select_schema_with_property_name;
use zeroclaw_api::jsonrpc::JSONRPC_VERSION;
fn make_rpc() -> (Arc<RpcOutbound>, mpsc::Receiver<String>) {
let (tx, rx) = mpsc::channel::<String>(16);
(Arc::new(RpcOutbound::new(tx)), rx)
}
#[tokio::test]
async fn name_returns_provided_name() {View on GitHub (pinned to 88bb9c8533)
Solutions
- Capture and log the raw outcome object to see the exact value the client sent.
- Align the client with the ACP permission response schema: {outcome: {outcome: selected|cancelled, ...}}.
- Map this error to a runtime Deny in the caller so the gated tool does not run.
- Add a client-side regression test that replies with only the two legal outcome kinds.
Defensive patterns
Strategy: fallback
Try / catch
// Any unparseable approval outcome is a runtime Deny (like 'cancelled'),
// attributed to the runtime rather than the operator.
let resp = ch.request_approval(recipient, &req).await
.unwrap_or(Some(ChannelApprovalResponse::Deny))
.unwrap_or(ChannelApprovalResponse::Deny); Prevention
- Treat any unparseable approval outcome as Deny, never Approve
- Pin protocol revisions between ZeroClaw and the client
- Validate client response shapes in integration tests before shipping
When it happens
Trigger: session/request_permission for an approval returns an outcome object with an unknown kind string or without the nested outcome/outcome field — e.g. a client sending a flat or differently-named shape.
Common situations: Protocol revision drift after a client update; incomplete client implementations; response shapes copied from a different JSON-RPC method.
Related errors
- ACP returned unexpected outcome: {other}
- ACP returned unknown permission optionId: {other}
- ACP elicitation/create failed: {} ({})
- ACP elicitation/create (multi) failed: {} ({})
- ACP request_permission timed out after {:?}
AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23).
Data as JSON: /api/errors/b41f668492523b55.
Report an issue: GitHub.