{"record":{"id":"84ff0bd00aaa4b9d","repo":"zeroclaw-labs/zeroclaw","slug":"acpchannel-request-multi-choice-requires-at-least","errorCode":null,"errorMessage":"AcpChannel.request_multi_choice requires at least one choice","messagePattern":"AcpChannel\\.request_multi_choice requires at least one choice","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/zeroclaw-channels/src/acp_channel.rs","lineNumber":369,"sourceCode":"        if self.client_caps.form {\n            self.request_choice_via_elicitation(question, choices, timeout)\n                .await\n        } else {\n            self.request_choice_via_permission(question, choices, timeout)\n                .await\n        }\n    }\n\n    async fn request_multi_choice(\n        &self,\n        question: &str,\n        choices: &[String],\n        min_items: usize,\n        max_items: usize,\n        timeout: Duration,\n    ) -> anyhow::Result<Option<Vec<String>>> {\n        if choices.is_empty() {\n            anyhow::bail!(\"AcpChannel.request_multi_choice requires at least one choice\")\n        }\n        if !self.client_caps.form {\n            // No legacy fallback for multi-select — session/request_permission\n            // is single-select-only. Signal Ok(None) so the caller (poll tool)\n            // takes its own non-ACP fallback path.\n            return Ok(None);\n        }\n\n        let req = ElicitationRequest {\n            session_id: self.session_id.clone(),\n            mode: ElicitationMode::Form,\n            message: question.to_string(),\n            requested_schema: multi_select_schema(choices, min_items, max_items),\n        };\n        let params = serde_json::to_value(&req)?;\n        let call = self.rpc.request(\"elicitation/create\", params);\n        let response_value = match tokio::time::timeout(timeout, call).await {\n            Ok(Ok(value)) => value,","sourceCodeStart":351,"sourceCodeEnd":387,"githubUrl":"https://github.com/zeroclaw-labs/zeroclaw/blob/88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc/crates/zeroclaw-channels/src/acp_channel.rs#L351-L387","documentation":"request_multi_choice rejects an empty choices slice before consulting capabilities. Note the ordering: the emptiness check runs first, so even a client without elicitation.form — which would otherwise receive Ok(None) so the poll tool can take its own non-ACP fallback — still gets an error on an empty list.","triggerScenarios":"The poll tool or another caller builds a multi-select list dynamically and passes an empty slice to request_multi_choice, regardless of whether the client advertised elicitation.form.","commonSituations":"Tag/category pickers whose source list is empty after filtering; batch-selection prompts over an empty result set; refactors that drop the guard at the call site.","solutions":["Guard the call: if the list is empty, return early or skip the question rather than invoking request_multi_choice.","Fix the upstream list producer so multi-select questions always have candidates.","Remember the adjacent contract: Ok(None) from this API means capability-missing fallback, so keep the empty-list check separate from that path."],"exampleFix":"// before\nlet picks = ch.request_multi_choice(\"Pick tags\", &tags, 1, 3, timeout).await?;\n\n// after\nif tags.is_empty() {\n    return Ok(None); // nothing to select; let the caller skip\n}\nlet picks = ch.request_multi_choice(\"Pick tags\", &tags, 1, 3, timeout).await?;","handlingStrategy":"validation","validationCode":"fn ensure_multi_choices(choices: &[String]) -> anyhow::Result<()> {\n    if choices.is_empty() {\n        anyhow::bail!(\"multi-select requires at least one option\");\n    }\n    Ok(())\n}","typeGuard":"fn has_options(choices: &[String]) -> bool {\n    !choices.is_empty()\n}","tryCatchPattern":null,"preventionTips":["Keep the empty-list guard separate from the Ok(None) capability-missing fallback so the two paths are not confused","Derive min/max bounds from the choices length so an empty list never reaches the prompt","Test the poll tool with empty candidate sets"],"tags":["rust","acp","input-validation","multi-select","empty-argument"],"backgroundTag":"empty-required-parameter","analyzedSha":"88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc","analyzedAt":"2026-08-23T01:07:41.857Z","schemaVersion":2},"datasetVersion":"2026-08-23T08:06:27.607Z"}