Hmbown/CodeWhale · error · anyhow::Error

; reconnect failed

Error message

{err:#}; reconnect failed: {reconnect_err:#}

What it means

When a tool call fails because the connection dropped, the pool attempts a reconnect; if that reconnect also fails, this error chains both — the original call error first, then the reconnect failure — so neither diagnosis is swallowed.

Solutions

  1. Read the original error (the first segment) — fix that root cause first, since reconnect only failed as a consequence
  2. Verify the server can start at all (run its command manually, check auth/env)
  3. Restart the session or remove/re-add the server config to force a clean reconnect
Defensive patterns

Strategy: try-catch

Validate before calling

// verify the server is reachable before heavy tool use
if !conn.is_ready() { conn.reconnect().await?; }

Try / catch

match call_tool(tool, args).await {
    Err(call_err) => match reconnect().await {
        Ok(fresh) => fresh.call_tool(tool, args).await,
        Err(re) => Err(anyhow!("{call_err:#}; reconnect failed: {re:#}")),
    },
    ok => ok,
}

Prevention

When it happens

Trigger: conn.call_tool fails, the pool's reconnect path is entered, and the reconnect itself returns Err(reconnect_err); both messages are combined.

Common situations: MCP server process crashed mid-session and refuses to restart (bad command, port conflict, missing credentials); network drop with a broken config underneath.

Understand the failure class

Background: ECONNREFUSED and "connection refused" / "could not connect to server" errors: what they mean and how to fix them — this error's family across 44 libraries.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22). Data as JSON: /api/errors/218894d1d0fe9901. Report an issue: GitHub.

Appendix: source

Thrown at crates/tui/src/mcp.rs:4972

                                .config()
                                .reviewed_plugin
                                .as_ref()
                                .map(|source| &source.authority)
                                != route.plugin_authority.as_ref()
                            || !conn.config().is_tool_enabled(&tool_name)
                            || !conn.tools().iter().any(|tool| tool.name == tool_name)
                        {
                            Err(anyhow::anyhow!(
                                "MCP tool '{tool_name}' is disabled for server '{server_name}'"
                            ))
                        } else {
                            let timeout = conn.config().effective_execute_timeout(&global_timeouts);
                            conn.call_tool(&tool_name, arguments, timeout).await
                        }
                    }
                    // A reconnect that fails must not swallow the call error
                    // that triggered it: report both, original first.
                    Err(reconnect_err) => Err(anyhow::anyhow!(
                        "{err:#}; reconnect failed: {reconnect_err:#}"
                    )),
                }
            }
            Err(err) => Err(err),
        };
        // A credential the server stopped accepting mid-session (revoked or
        // rotated elsewhere, refresh rejected) is the same `◆ auth required`
        // class as a failed connect: land it in the same typed state so the
        // next catalog offers the login tool instead of a dead error.
        result.map_err(|err| self.note_live_call_failure(&server_name, err))
    }

    /// Get list of configured server names (static + dynamic)
    #[allow(dead_code)] // Public API for MCP consumers
    pub fn server_names(&self) -> Vec<String> {
        let mut names: Vec<String> = self
            .config

View on GitHub (pinned to 73e0f67d83)