{"record":{"id":"db944a1d5404958a","repo":"zeroclaw-labs/zeroclaw","slug":"acp-returned-unknown-permission-optionid-other","errorCode":null,"errorMessage":"ACP returned unknown permission optionId: {other}","messagePattern":"ACP returned unknown permission optionId: (.+?)","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/zeroclaw-channels/src/acp_channel.rs","lineNumber":528,"sourceCode":"                let option_id = outcome\n                    .and_then(|o| o.get(\"optionId\"))\n                    .and_then(|s| s.as_str())\n                    .unwrap_or(\"\");\n                // \"selected\" means the operator picked one of the options we\n                // offered, so every arm here is a genuine operator decision.\n                let response = match option_id {\n                    \"allow-once\" => ChannelApprovalResponse::Approve,\n                    \"allow-always\" => ChannelApprovalResponse::AlwaysApprove,\n                    \"reject-once\" | \"reject-always\" => ChannelApprovalResponse::Deny,\n                    \"reject-with-edit\" => {\n                        let replacement = outcome\n                            .and_then(|o| o.get(\"replacementContent\"))\n                            .and_then(|s| s.as_str())\n                            .unwrap_or(\"\")\n                            .to_string();\n                        ChannelApprovalResponse::DenyWithEdit { replacement }\n                    }\n                    other => anyhow::bail!(\"ACP returned unknown permission optionId: {other}\"),\n                };\n                Ok(Some(\n                    zeroclaw_api::channel::AttributedApprovalResponse::operator(response),\n                ))\n            }\n            // The client withdrew the prompt; nobody chose anything. Still a\n            // deny, but the runtime's, not the operator's.\n            \"cancelled\" => Ok(Some(\n                zeroclaw_api::channel::AttributedApprovalResponse::from_runtime(\n                    ChannelApprovalResponse::Deny,\n                    zeroclaw_api::channel::ApprovalSource::Unreachable,\n                ),\n            )),\n            other => anyhow::bail!(\"ACP returned unexpected permission outcome: {other}\"),\n        }\n    }\n}\n","sourceCodeStart":510,"sourceCodeEnd":546,"githubUrl":"https://github.com/zeroclaw-labs/zeroclaw/blob/88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc/crates/zeroclaw-channels/src/acp_channel.rs#L510-L546","documentation":"The client answered an approval prompt with an optionId outside the offered set (allow-once, allow-always, reject-once, reject-always, reject-with-edit). ZeroClaw only recognizes the optionIds it sent, so any other id — including an empty string when the response omits optionId — is treated as a client-side protocol violation, not an operator decision.","triggerScenarios":"session/request_permission replies with outcome \"selected\" but a made-up or missing optionId: custom clients inventing ids like \"yes\", response-building bugs, or a client echoing ids from a different concurrent permission prompt.","commonSituations":"Hand-rolled ACP client implementations; client-side prompt queues mixing up concurrent permission requests; a spec revision renaming option kinds that ZeroClaw does not yet map.","solutions":["Log the offending optionId shown in the error and compare it against the optionIds ZeroClaw offered in the request.","Fix the client to echo one of the optionId values from the request's options array verbatim.","In the caller, map this error to Deny (fail closed) so an ambiguous answer never approves a tool.","If a legitimate new option kind appeared, upgrade ZeroClaw to a revision that maps it."],"exampleFix":"// before\nlet resp = ch.request_approval(recipient, &req).await?;\n\n// after\nlet resp = match ch.request_approval(recipient, &req).await {\n    Ok(r) => r,\n    Err(e) => {\n        tracing::warn!(error = %e, \"unrecognized approval answer; denying\");\n        return Ok(Some(ChannelApprovalResponse::Deny));\n    }\n};","handlingStrategy":"fallback","validationCode":null,"typeGuard":null,"tryCatchPattern":"// An unrecognized approval answer is ambiguous: deny rather than guess.\nlet resp = ch.request_approval(recipient, &req).await\n    .unwrap_or(Some(ChannelApprovalResponse::Deny))\n    .unwrap_or(ChannelApprovalResponse::Deny);","preventionTips":["In client implementations, echo optionIds verbatim from the request options","Never map an unknown approval answer to approve","Correlate responses by JSON-RPC request id when multiple prompts are in flight"],"tags":["rust","acp","approval","protocol-violation","permissions"],"backgroundTag":"invalid-protocol-response","analyzedSha":"88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc","analyzedAt":"2026-08-23T01:07:41.857Z","schemaVersion":2},"datasetVersion":"2026-08-23T08:06:27.607Z"}