Hmbown/CodeWhale · error · anyhow::Error

Failed to store MCP connection for

Error message

Failed to store MCP connection for {server_name}

What it means

store_ready_connection() succeeded in inserting the newly connected McpConnection, but the immediately following self.connections.get_mut(server_name) — used to return a mutable handle — found nothing. This is an internal race/invariant break: the just-stored connection was removed between the two steps.

Solutions

  1. Retry the call; the pool will reconnect on demand.
  2. Serialize reconnect/store operations for a given server under one lock.
  3. Return the connection directly from store_ready_connection instead of re-fetching from the map.
  4. If it reproduces without concurrency, file a bug with logs.
Defensive patterns

Strategy: retry

Try / catch

match pool.call_tool(server, tool, input) {
    Err(e) if e.to_string().contains("Failed to store MCP connection") => {
        // race with pool mutation: retry; the pool will reconnect
        pool.call_tool(server, tool, input).await?
    }
    other => other?,
}

Prevention

When it happens

Trigger: Concurrent code removing or taking entries from pool.connections (teardown, reconnect of another path, dynamic server removal) in the window between store_ready_connection and get_mut.

Common situations: Pool shutdown racing a lazy connect; two tasks reconnecting the same server simultaneously; dynamic-server eviction concurrent with first use.

Understand the failure class

Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.

Related errors


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

Appendix: source

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

            Err(error) => {
                self.note_connect_failure(server_name, &error);
                if let Some(previous) = previous_connection {
                    tracing::debug!(
                        target: "mcp",
                        server = %server_name,
                        "reconnect failed; restored the previous MCP connection and its last-good catalog"
                    );
                    self.connections.insert(server_name.to_string(), previous);
                }
                return Err(error);
            }
        };
        connection.catalog_generation = self.catalog_generation.load(Ordering::SeqCst);

        self.store_ready_connection(server_name.to_string(), connection)?;
        self.connections
            .get_mut(server_name)
            .ok_or_else(|| anyhow::anyhow!("Failed to store MCP connection for {server_name}"))
    }

    /// Retry exactly one server against the configuration already owned by
    /// this pool.
    ///
    /// Unlike normal lazy tool dispatch, an explicit row retry must not notice
    /// a concurrent config mtime and invalidate healthy siblings. Config edits
    /// remain owned by the explicit reload path; this operation only replaces
    /// the named transport.
    pub async fn retry_connection(&mut self, server_name: &str) -> Result<&mut McpConnection> {
        self.require_server(server_name)?;
        // A person asked for this one by name. Clear the cooldown so the
        // attempt happens now and, if it fails again, the ladder restarts
        // from the short end rather than from wherever it had climbed to.
        // Explicit intent restarts supervision: the cooldown, the dead mark,
        // and any park all clear, so the supervisor resumes watching whatever
        // this retry stores — or stays quiet while the server is connectionless.
        self.connect_backoff.remove(server_name);

View on GitHub (pinned to 73e0f67d83)