{"record":{"id":"48539933ec5b121b","repo":"zeroclaw-labs/zeroclaw","slug":"acpchannel-request-choice-requires-at-least-one-ch","errorCode":null,"errorMessage":"AcpChannel.request_choice requires at least one choice","messagePattern":"AcpChannel\\.request_choice requires at least one choice","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/zeroclaw-channels/src/acp_channel.rs","lineNumber":349,"sourceCode":"        &self,\n        _channel_id: &str,\n        _message_id: &str,\n        _emoji: &str,\n    ) -> anyhow::Result<()> {\n        anyhow::bail!(\"AcpChannel does not support reactions\")\n    }\n\n    async fn request_choice(\n        &self,\n        question: &str,\n        choices: &[String],\n        timeout: Duration,\n    ) -> anyhow::Result<Option<String>> {\n        if choices.is_empty() {\n            // Caller should already gate on this via supports_free_form_ask,\n            // but be defensive — both downstream paths require at least one\n            // option to present.\n            anyhow::bail!(\"AcpChannel.request_choice requires at least one choice\")\n        }\n        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>>> {","sourceCodeStart":331,"sourceCodeEnd":367,"githubUrl":"https://github.com/zeroclaw-labs/zeroclaw/blob/88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc/crates/zeroclaw-channels/src/acp_channel.rs#L331-L367","documentation":"request_choice rejects an empty choices slice before doing any protocol work — both downstream paths (form elicitation and the legacy permission overload) need at least one option to present. The source comment notes callers should already gate on this; the bail is a defensive invariant for callers that do not.","triggerScenarios":"An agent tool dynamically builds the option list (search results, filtered items) and the list comes back empty, then request_choice is called with an empty slice — the check fires identically on both the form-capable and legacy paths.","commonSituations":"Dynamic option sources that can legitimately return zero items (empty search results, filtered schedules); refactors that move the emptiness check away from the call site; generated prompts whose choices template rendered nothing.","solutions":["Check choices.is_empty() before calling and take a caller-side action: skip the question, use a default, or answer free-form if the channel supports it.","Fix the upstream producer so a question that must be asked never has an empty option set.","If the question is optional, skip asking it entirely rather than prompting with no options."],"exampleFix":"// before\nlet pick = ch.request_choice(\"Which release?\", &options, timeout).await?;\n\n// after\nif options.is_empty() {\n    tracing::warn!(\"no release candidates; skipping question\");\n    return Ok(None);\n}\nlet pick = ch.request_choice(\"Which release?\", &options, timeout).await?;","handlingStrategy":"validation","validationCode":"fn ensure_choices(choices: &[String]) -> anyhow::Result<()> {\n    if choices.is_empty() {\n        anyhow::bail!(\"question requires at least one option\");\n    }\n    Ok(())\n}","typeGuard":"fn has_options(choices: &[String]) -> bool {\n    !choices.is_empty()\n}","tryCatchPattern":null,"preventionTips":["Validate option lists at the tool boundary where they are built","Add a standing escape option (e.g. Cancel) when a list may be empty","Unit-test dynamic option producers for the zero-result case"],"tags":["rust","acp","input-validation","request-choice","empty-argument"],"backgroundTag":"empty-required-parameter","analyzedSha":"88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc","analyzedAt":"2026-08-23T01:07:41.857Z","schemaVersion":2},"datasetVersion":"2026-08-23T08:06:27.607Z"}