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

MCP server `{server_name}` timed out after {timeout_secs}s d

Error message

MCP server `{server_name}` timed out after {timeout_secs}s during {operation}; outcome unknown and request was not replayed

What it means

The JSON-RPC request was written to the server but no response arrived within the configured timeout. Because the server may still be processing it, the client deliberately does not replay the request; it arms an asynchronous recovery (transport reset + re-handshake) and bails, telling the caller the operation may or may not have taken effect.

Source

Thrown at crates/zeroclaw-tools/src/mcp_client.rs:661

            match send_result {
                Err(_) => {
                    let unknown_epoch = lifecycle.outcome_unknown_epoch();
                    cancellation_guard.disarm();
                    ::zeroclaw_log::record!(
                        WARN,
                        ::zeroclaw_log::Event::new(module_path!(), ::zeroclaw_log::Action::Timeout)
                            .with_outcome(::zeroclaw_log::EventOutcome::Failure)
                            .with_attrs(::serde_json::json!({
                                "mcp_server": &server_name,
                                "rpc_method": rpc_method,
                                "timeout_secs": timeout_secs,
                                "outcome_unknown": unknown_epoch.is_some(),
                            })),
                        "mcp_client: MCP request timed out"
                    );
                    if let Some(epoch) = unknown_epoch {
                        self.spawn_recovery(epoch, operation.to_string());
                        bail!(
                            "MCP server `{server_name}` timed out after {timeout_secs}s during \
                             {operation}; outcome unknown and request was not replayed"
                        );
                    }
                    bail!(
                        "MCP server `{server_name}` timed out after {timeout_secs}s before writing \
                         {operation}"
                    );
                }
                Ok(Ok(response)) => {
                    cancellation_guard.disarm();
                    return Ok(response);
                }
                Ok(Err(error)) => {
                    if let Some(epoch) = lifecycle.outcome_unknown_epoch() {
                        cancellation_guard.disarm();
                        self.spawn_recovery(epoch, operation.to_string());
                        return Err(error).with_context(|| {

View on GitHub (pinned to 88bb9c8533)

Solutions

  1. Raise the per-tool timeout above the tool's realistic worst-case execution time
  2. Check server-side logs to learn whether the operation actually completed before deciding to retry
  3. Retry only idempotent operations directly; for writes, verify the effect on the server first — the client guarantees no automatic replay
  4. If it recurs, profile why the server is slow rather than only raising the ceiling

Example fix

# before
tool_timeout_secs = 30

# after — sized to the slowest advertised tool
tool_timeout_secs = 180
Defensive patterns

Strategy: retry

Type guard

fn is_outcome_unknown_timeout(err: &anyhow::Error) -> bool {
    err.to_string().contains("outcome unknown and request was not replayed")
}

Try / catch

On this error the operation is ambiguous: retry reads directly once recovery completes; for writes, query the server for the effect (list the changed state) and only then re-issue — never blind-retry, the client intentionally does not replay.

Prevention

When it happens

Trigger: Slow tools (LLM-backed, large file scans) exceeding timeout_secs; a hung or deadlocked server; network drop after the request was sent; timeouts configured below the tool's documented worst case.

Common situations: Default timeouts used with heavyweight tools; servers that queue requests behind long jobs; transport stalls on loaded machines.

Understand the failure class

Related errors


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