zeroclaw-labs/zeroclaw · error · anyhow::Error
AcpChannel.request_choice requires at least one choice
Error message
AcpChannel.request_choice requires at least one choice
What it means
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.
Source
Thrown at crates/zeroclaw-channels/src/acp_channel.rs:349
&self,
_channel_id: &str,
_message_id: &str,
_emoji: &str,
) -> anyhow::Result<()> {
anyhow::bail!("AcpChannel does not support reactions")
}
async fn request_choice(
&self,
question: &str,
choices: &[String],
timeout: Duration,
) -> anyhow::Result<Option<String>> {
if choices.is_empty() {
// Caller should already gate on this via supports_free_form_ask,
// but be defensive — both downstream paths require at least one
// option to present.
anyhow::bail!("AcpChannel.request_choice requires at least one choice")
}
if self.client_caps.form {
self.request_choice_via_elicitation(question, choices, timeout)
.await
} else {
self.request_choice_via_permission(question, choices, timeout)
.await
}
}
async fn request_multi_choice(
&self,
question: &str,
choices: &[String],
min_items: usize,
max_items: usize,
timeout: Duration,
) -> anyhow::Result<Option<Vec<String>>> {View on GitHub (pinned to 88bb9c8533)
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.
Example fix
// before
let pick = ch.request_choice("Which release?", &options, timeout).await?;
// after
if options.is_empty() {
tracing::warn!("no release candidates; skipping question");
return Ok(None);
}
let pick = ch.request_choice("Which release?", &options, timeout).await?; Defensive patterns
Strategy: validation
Validate before calling
fn ensure_choices(choices: &[String]) -> anyhow::Result<()> {
if choices.is_empty() {
anyhow::bail!("question requires at least one option");
}
Ok(())
} Type guard
fn has_options(choices: &[String]) -> bool {
!choices.is_empty()
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- AcpChannel.request_multi_choice requires at least one choice
- ACP returned unexpected outcome: {other}
- ACP elicitation/create failed: {} ({})
- ACP elicitation/create timed out after {timeout:?}
- AcpChannel.listen is not supported (free-form ask_user await
AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23).
Data as JSON: /api/errors/48539933ec5b121b.
Report an issue: GitHub.