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

ACP elicitation/create timed out after

Error message

ACP elicitation/create timed out after {timeout:?}

What it means

The client never answered the elicitation/create request within the caller-supplied timeout. ZeroClaw wraps every elicitation call in tokio::time::timeout so an unresponsive client (user walked away, IDE closed, transport stalled) cannot park the agent turn forever. The elapsed Duration is included in the message.

Solutions

  1. Raise the timeout passed to request_choice to comfortably exceed expected human response time (minutes, not seconds, for interactive prompts).
  2. Verify the ACP client process and transport are alive; a dead back channel can never deliver a response.
  3. Treat this error as a cancellation (None) in the caller so the agent proceeds without user input instead of failing.
  4. If prompts routinely expire, pre-ask availability or route the question through a channel with push notifications.

Example fix

// before
let pick = ch.request_choice("Deploy to prod?", &choices, Duration::from_secs(10)).await?;

// after
let pick = match ch.request_choice("Deploy to prod?", &choices, Duration::from_secs(300)).await {
    Ok(opt) => opt,
    Err(e) if e.to_string().contains("timed out") => {
        tracing::warn!(error = %e, "no answer in time; treating as cancel");
        None
    }
    Err(e) => return Err(e),
};
Defensive patterns

Strategy: retry

Try / catch

match ch.request_choice(q, &choices, timeout).await {
    Ok(opt) => opt,
    Err(e) if e.to_string().contains("timed out") => {
        // one re-ask, then treat as user cancel
        ch.request_choice(q, &choices, timeout).await.unwrap_or(None)
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: request_choice takes the elicitation path and no JSON-RPC response arrives before the Duration passed to request_choice elapses — the user leaves the form open, the client process exits without cancelling, or the back channel silently dies.

Common situations: Timeouts sized for network round-trips instead of human response time; modal dialogs hidden behind other windows; laptop sleep mid-prompt; dead stdio/WebSocket transport that never delivers the response.

Understand the failure class

Background: Request timed out: what client-side request timeouts mean across libraries (Request timed out, TIMED_OUT, APITimeoutError) — this error's family across 39 libraries.

Related errors


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

Appendix: source

Thrown at crates/zeroclaw-channels/src/acp_channel.rs:152

        let req = ElicitationRequest {
            session_id: self.session_id.clone(),
            mode: ElicitationMode::Form,
            message: question.to_string(),
            requested_schema: single_select_schema(choices),
        };
        debug_assert!(
            matches!(req.mode, ElicitationMode::Form),
            "Phase 1 must not emit URL-mode elicitation"
        );

        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!("ACP elicitation/create failed: {} ({})", e.message, e.code)
            }
            Err(_) => anyhow::bail!("ACP elicitation/create 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 text =
                    zeroclaw_api::elicitation::decode_single_select_accept(&content, choices)?;
                Ok(Some(text))
            }
            ElicitationResponse::Decline | ElicitationResponse::Cancel => Ok(None),
        }
    }
}

impl ::zeroclaw_api::attribution::Attributable for AcpChannel {
    fn role(&self) -> ::zeroclaw_api::attribution::Role {
        ::zeroclaw_api::attribution::Role::Channel(

View on GitHub (pinned to 88bb9c8533)