zeroclaw-labs/zeroclaw · error

RPC elicitation/create (multi) timed out after {timeout:?}

Error message

RPC elicitation/create (multi) timed out after {timeout:?}

What it means

The multi-select elicitation call `elicitation/create` did not complete within the caller-supplied timeout (tokio::time::timeout elapsed). The connected client never answered the form and the connection did not surface an error, so the runtime gives up rather than blocking the approval flow indefinitely.

Source

Thrown at crates/zeroclaw-runtime/src/rpc/approval_channel.rs:278

        timeout: Duration,
    ) -> anyhow::Result<Option<Vec<String>>> {
        let req = ElicitationRequest {
            session_id: self.session_id.clone(),
            mode: ElicitationMode::Form,
            message: question.to_string(),
            requested_schema: multi_select_schema(choices, min_items, max_items),
        };
        let params = serde_json::to_value(&req)?;
        let call = self.rpc.request("elicitation/create", params);
        let response_value = match tokio::time::timeout(timeout, call).await {
            Ok(Ok(value)) => value,
            Ok(Err(e)) => anyhow::bail!(
                "RPC elicitation/create (multi) failed: {} ({})",
                e.message,
                e.code
            ),
            Err(_) => {
                anyhow::bail!("RPC elicitation/create (multi) timed out after {timeout:?}")
            }
        };
        let parsed: ElicitationResponse = serde_json::from_value(response_value)
            .map_err(|e| anyhow::Error::msg(format!("malformed elicitation response: {e}")))?;
        match parsed {
            ElicitationResponse::Accept { content } => {
                let texts = decode_multi_select_accept(&content, choices)?;
                Ok(Some(texts))
            }
            ElicitationResponse::Decline | ElicitationResponse::Cancel => Ok(None),
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use std::sync::Arc;

View on GitHub (pinned to 88bb9c8533)

Solutions

  1. Increase the timeout passed to request_multi_choice to match the expected human response window.
  2. Verify client connectivity (heartbeats/pings) before issuing the ask.
  3. Ensure the client event loop is not blocked while a form is pending.
  4. Treat timeout as 'no selection' (the single-select approval path maps timeout to Deny/TimedOut) and decide the fallback behavior explicitly.

Example fix

// before
let picks = channel
    .request_multi_choice(q, &choices, 1, 3, Duration::from_secs(30))
    .await?;

// after — allow a real operator window, treat timeout as no selection
match channel
    .request_multi_choice(q, &choices, 1, 3, Duration::from_secs(300))
    .await
{
    Ok(Some(picks)) => { /* proceed */ }
    Ok(None) => { /* declined/cancelled */ }
    Err(e) if e.to_string().contains("timed out") => {
        tracing::warn!("elicitation timed out; continuing without selection");
    }
    Err(e) => return Err(e),
}
Defensive patterns

Strategy: retry

Validate before calling

// Before asking, confirm the client recently answered a ping/heartbeat
// and size the timeout to the human response window, not a network RTT.

Try / catch

Err(e) if e.to_string().contains("(multi) timed out") => {
    // one retry for a stalled-but-alive connection, then treat as no-selection
}

Prevention

When it happens

Trigger: Operator is away or the client UI never renders the request; the websocket/stdio connection stalls without closing (NAT drop, suspended laptop); the client event loop is blocked so the response is produced but never delivered; timeout set shorter than realistic human response time.

Common situations: Short approval timeouts (e.g. 30s) used with human-in-the-loop prompts; client on an unstable network; backgrounded TUI/mobile client that stops pumping messages; load-test clients that never answer elicitations.

Understand the failure class

Related errors


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