{"record":{"id":"db3401a658c27b78","repo":"zeroclaw-labs/zeroclaw","slug":"acp-returned-unexpected-outcome-other","errorCode":null,"errorMessage":"ACP returned unexpected outcome: {other}","messagePattern":"ACP returned unexpected outcome: (.+?)","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/zeroclaw-channels/src/acp_channel.rs","lineNumber":121,"sourceCode":"            .and_then(|o| o.get(\"outcome\"))\n            .and_then(|s| s.as_str())\n            .unwrap_or(\"\");\n        match kind {\n            \"selected\" => {\n                let option_id = outcome\n                    .and_then(|o| o.get(\"optionId\"))\n                    .and_then(|s| s.as_str())\n                    .unwrap_or(\"\");\n                let idx = option_id\n                    .strip_prefix(\"choice-\")\n                    .and_then(|s| s.parse::<usize>().ok());\n                match idx.and_then(|i| choices.get(i)) {\n                    Some(text) => Ok(Some(text.clone())),\n                    None => anyhow::bail!(\"ACP returned unknown optionId: {option_id}\"),\n                }\n            }\n            \"cancelled\" => Ok(None),\n            other => anyhow::bail!(\"ACP returned unexpected outcome: {other}\"),\n        }\n    }\n\n    /// Form-mode elicitation path — issues `elicitation/create` with a\n    /// single-select schema. Used when the client advertises\n    /// `clientCapabilities.elicitation.form`.\n    async fn request_choice_via_elicitation(\n        &self,\n        question: &str,\n        choices: &[String],\n        timeout: Duration,\n    ) -> anyhow::Result<Option<String>> {\n        let req = ElicitationRequest {\n            session_id: self.session_id.clone(),\n            mode: ElicitationMode::Form,\n            message: question.to_string(),\n            requested_schema: single_select_schema(choices),\n        };","sourceCodeStart":103,"sourceCodeEnd":139,"githubUrl":"https://github.com/zeroclaw-labs/zeroclaw/blob/88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc/crates/zeroclaw-channels/src/acp_channel.rs#L103-L139","documentation":"Thrown by AcpChannel's legacy multiple-choice path (request_choice_via_permission) when the client's session/request_permission reply carries an outcome.outcome value other than \"selected\" or \"cancelled\". ZeroClaw maps each offered choice to a synthetic optionId (choice-0..n) and only understands those two outcome kinds; anything else — including a response that omits the nested outcome fields, which parses as an empty string — is treated as a client-side protocol violation. It usually indicates a buggy client, protocol revision drift, or a malformed JSON-RPC result.","triggerScenarios":"Calling Channel::request_choice on an AcpChannel whose client did not advertise clientCapabilities.elicitation.form (so the legacy session/request_permission overload is used), and the client replies with an unknown outcome kind, an empty outcome object, or a result shape missing outcome/outcome.","commonSituations":"Home-grown or early-version ACP clients (editors, orchestrators) sending values like \"dismissed\" or omitting the nested outcome object; protocol version drift after a client update; a client forwarding a permission response matched to the wrong request id.","solutions":["Log the raw outcome object from the response to see exactly which string the client sent before the match arm fired.","Update the ACP client to a protocol revision that replies with only selected or cancelled on session/request_permission.","Have the client advertise clientCapabilities.elicitation.form so the modern elicitation/create path is used instead of the legacy overload.","In the caller, degrade gracefully: log the error and treat it as a cancellation (None) rather than failing the whole agent turn."],"exampleFix":"// before\nlet pick = channel.request_choice(question, &choices, timeout).await?;\n\n// after\nlet pick = match channel.request_choice(question, &choices, timeout).await {\n    Ok(opt) => opt,\n    Err(e) if e.to_string().contains(\"unexpected outcome\") => {\n        tracing::warn!(error = %e, \"unrecognized ACP outcome; treating as cancel\");\n        None\n    }\n    Err(e) => return Err(e),\n};","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"match ch.request_choice(q, &choices, timeout).await {\n    Ok(Some(text)) => { /* proceed with the selection */ }\n    Ok(None) => { /* user cancelled */ }\n    Err(e) => {\n        tracing::warn!(error = %e, \"choice prompt failed; treating as cancelled\");\n        // treat as cancel: never guess a default option\n    }\n}","preventionTips":["Prefer clients that advertise elicitation.form so the legacy permission overload is not used","Pin ZeroClaw and the ACP client to the same protocol revision","Log the raw outcome object when integrating a new ACP client"],"tags":["rust","acp","agent-client-protocol","jsonrpc","protocol-violation","request-choice"],"backgroundTag":"invalid-protocol-response","analyzedSha":"88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc","analyzedAt":"2026-08-23T01:07:41.857Z","schemaVersion":2},"datasetVersion":"2026-08-23T08:06:27.607Z"}