Hmbown/CodeWhale · warning

MCP Streamable HTTP session expired; retry with a new sessio

Error message

MCP Streamable HTTP session expired; retry with a new session required ({detail})

What it means

When a POST on a streamable HTTP transport fails because the server considers the cached Mcp-Session-Id dead, HttpTransport::send clears the cached session ID and returns this error: the caller must retry, which establishes a fresh session and new ID. The manager's call_tool already performs one such retry internally (drop connection, reconnect, resend) for stale sessions; this error surfaces when a retry path still ends in a stale session.

Source

Thrown at crates/tui/src/mcp/http.rs:258

            HttpTransportMode::Streamable(transport) => match transport.send(msg.clone()).await {
                Ok(()) => Ok(()),
                Err(StreamableSendError::Incompatible(detail)) => {
                    tracing::debug!(
                        "MCP Streamable HTTP unavailable; falling back to SSE endpoint discovery: {}",
                        detail
                    );
                    self.switch_to_sse_and_send(msg).await
                }
                Err(StreamableSendError::StaleSession(detail)) => {
                    if let HttpTransportMode::Streamable(transport) = &mut self.mode {
                        tracing::debug!(
                            target: "mcp",
                            error = %detail,
                            "MCP Streamable HTTP session expired; clearing cached session ID"
                        );
                        transport.session_id = None;
                    }
                    Err(anyhow::anyhow!(
                        "MCP Streamable HTTP session expired; retry with a new session required ({detail})"
                    ))
                }
                Err(StreamableSendError::Other(err)) => Err(err),
            },
            HttpTransportMode::Sse(transport) => transport.send(msg).await,
        }
    }

    async fn recv(&mut self) -> Result<Vec<u8>> {
        match &mut self.mode {
            HttpTransportMode::Streamable(transport) => transport.recv().await,
            HttpTransportMode::Sse(transport) => transport.recv().await,
        }
    }

    async fn shutdown(&mut self) {
        if let HttpTransportMode::Sse(transport) = &mut self.mode {

View on GitHub (pinned to 8880682c63)

Solutions

  1. Retry the tool call - a new session is established automatically on the next attempt
  2. If it recurs often, keep the connection warm (periodic ping/list calls) or raise the server's session TTL
  3. For load-balanced servers, enable sticky sessions or a shared session store server-side
Defensive patterns

Strategy: retry

Try / catch

let mut attempts = 0;
loop {
    attempts += 1;
    match manager.call_tool(prefixed_name, args.clone()).await {
        Ok(out) => break Ok(out),
        Err(e) if e.to_string().contains("session expired; retry with a new session required") && attempts < 3 => continue,
        Err(e) => break Err(e),
    }
}

Prevention

When it happens

Trigger: A streamable HTTP server expires or forgets sessions (restart, TTL, load-balancer failover) while codewhale holds a cached session ID; the next send gets the stale-session response and the automatic retry also loses the race.

Common situations: Server restarts or autoscaling behind a load balancer; idle periods exceeding the server's session TTL; deployments where the session store is not shared across instances.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@8880682c63 (2026-08-16). Data as JSON: /api/errors/8cd142b444096b68. Report an issue: GitHub.