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

MCP server `{server_name}` exhausted the {timeout_secs}s bud

Error message

MCP server `{server_name}` exhausted the {timeout_secs}s budget recovering before writing {operation}

What it means

Before this request could be written, a transport error triggered an automatic reconnect; dispatch_rpc waits for that recovery within the same overall deadline, and this error means recovery consumed the entire remaining budget without finishing. The request was never sent — retry is safe — but the connection was still unhealthy at the deadline.

Source

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

                    cancellation_guard.disarm();
                    let recoverable = error.downcast_ref::<McpTransportError>().is_some();
                    if recoverable && pre_write_retries < MAX_RECONNECT_ATTEMPTS {
                        pre_write_retries += 1;
                        let observed_epoch = lifecycle.pre_write_epoch().unwrap_or(0);
                        let recovery = self.start_recovery(observed_epoch, operation.to_string());
                        match timeout_at(deadline, recovery).await {
                            Ok(Ok(result)) => result?,
                            Ok(Err(join_error)) => {
                                return Err(anyhow::Error::new(join_error)).with_context(|| {
                                    format!(
                                        "MCP server `{server_name}` recovery task failed before \
                                         writing {operation}"
                                    )
                                });
                            }
                            Err(_) => {
                                bail!(
                                    "MCP server `{server_name}` exhausted the {timeout_secs}s \
                                     budget recovering before writing {operation}"
                                );
                            }
                        }
                        continue;
                    }
                    return Err(error).with_context(|| {
                        format!("MCP server `{server_name}` error during {operation}")
                    });
                }
            }
        }
    }

    /// Call a tool on this server. Returns the raw JSON result.
    pub async fn call_tool(
        &self,

View on GitHub (pinned to 88bb9c8533)

Solutions

  1. Retry once the server has finished starting; the request was not written so there is no duplicate risk
  2. Increase timeout_secs to cover the server's cold-start plus re-handshake time
  3. Make the server start faster (lazy imports, fewer deps) instead of only raising timeouts
  4. If it persists, check for crash-looping: each failed start wastes a full budget window
Defensive patterns

Strategy: retry

Type guard

fn is_recovery_budget_exhausted(err: &anyhow::Error) -> bool {
    err.to_string().contains("exhausted the") && err.to_string().contains("budget recovering")
}

Try / catch

Retry once after giving the server time to finish starting — the request was never written. Persistent failures mean the server cold-start or crash-loop exceeds the budget; fix that before more retries.

Prevention

When it happens

Trigger: A slow-starting server process (stdio spawn, dependency loading) whose reset + re-handshake exceeds the remaining budget; recovery racing a nearly-expired deadline; very small timeout values paired with cold-starting servers.

Common situations: Heavy Node/Python MCP servers cold-starting slower than the configured timeout; crash-looping servers where every recovery attempt restarts them and burns the full budget.

Understand the failure class

Related errors


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