zeroclaw-labs/zeroclaw · warning · anyhow::Error

Signal poll requires at least 2 options (got {}); render as

Error message

Signal poll requires at least 2 options (got {}); render as text instead

What it means

Raised by SignalChannel::send_poll when options.len() < 2 — a deliberate validation guard before build_poll_params and the sendPollCreate RPC. The message itself tells the caller what to do: 'render as text instead'. It fires from send_choice when the model/agent produced fewer than two options, since a one-option (or zero-option) poll is meaningless on Signal. It is a control-flow signal, not an infrastructure fault.

Source

Thrown at crates/zeroclaw-channels/src/signal.rs:544

    /// Sent via signal-cli daemon's JSON-RPC `sendPollCreate` method. The
    /// poll renders as native UI in modern Signal clients and emits a
    /// poll-vote event (`pollAnswer` or `pollVote`, depending on signal-cli
    /// version) back through the SSE stream when the user votes — see
    /// `process_envelope` for how that flows back to consumers, normally as
    /// a synthetic `[choice-index]N` `ChannelMessage`.
    ///
    /// `multiple_choice = false` → single-select poll (the common case
    /// for "pick one of N" agent prompts). Pass `true` to allow
    /// multi-select.
    pub async fn send_poll(
        &self,
        recipient: &str,
        question: &str,
        options: &[String],
        multiple_choice: bool,
    ) -> anyhow::Result<()> {
        if options.len() < 2 {
            anyhow::bail!(
                "Signal poll requires at least 2 options (got {}); render as text instead",
                options.len()
            );
        }
        let params = self.build_poll_params(recipient, question, options, multiple_choice);
        self.rpc_request("sendPollCreate", params).await?;
        Ok(())
    }
}

impl ::zeroclaw_api::attribution::Attributable for SignalChannel {
    fn role(&self) -> ::zeroclaw_api::attribution::Role {
        ::zeroclaw_api::attribution::Role::Channel(::zeroclaw_api::attribution::ChannelKind::Signal)
    }
    fn alias(&self) -> &str {
        &self.alias
    }
}

View on GitHub (pinned to 88bb9c8533)

Solutions

  1. Catch this error in send_choice and fall back to a plain text message rendering the question and the available options
  2. Validate options.len() >= 2 before calling send_poll and branch to text rendering yourself
  3. Fix the upstream choice generation so it always yields at least two options

Example fix

// before
self.send_poll(recipient, question, options, multiple_choice).await?;

// after
if options.len() >= 2 {
    self.send_poll(recipient, question, options, multiple_choice).await
} else {
    let text = format!("{question}\n{}", options.join("\n"));
    self.send(recipient, &text).await
}
Defensive patterns

Strategy: validation

Validate before calling

fn render_choice(channel: &SignalChannel, recipient: &str, question: &str, options: &[String], multi: bool) -> BoxFuture<'_, anyhow::Result<()>> {
    async move {
        if options.len() >= 2 {
            channel.send_poll(recipient, question, options, multi).await
        } else {
            let text = format!("{question}\n{}", options.join("\n"));
            channel.send(recipient, &text).await
        }
    }
    .boxed()
}

Type guard

fn poll_options_valid(options: &[String]) -> bool {
    options.len() >= 2 && options.iter().all(|o| !o.trim().is_empty())
}

Try / catch

if let Err(err) = channel.send_poll(recipient, question, options, multi).await {
    if format!("{err:#}").starts_with("Signal poll requires at least 2 options") {
        let text = format!("{question}\n{}", options.join("\n"));
        return channel.send(recipient, &text).await; // fallback to plain text
    }
    return Err(err);
}

Prevention

When it happens

Trigger: send_choice calls send_poll with an options slice containing 0 or 1 entries — e.g. an LLM produced a single choice, a choice list was truncated, or options were parsed out of malformed output.

Common situations: Agent-generated choice prompts collapsing to one option; empty options after filtering/escaping; upstream prompt templates that allow a single-item list.

Related errors


AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23). Data as JSON: /api/errors/bd551529bca93f61. Report an issue: GitHub.