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
- Retry the tool call - a new session is established automatically on the next attempt
- If it recurs often, keep the connection warm (periodic ping/list calls) or raise the server's session TTL
- 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
- Retry once or twice on this error - the cached session ID is already cleared, so the next attempt opens a fresh session
- Keep streamable HTTP connections warm with periodic calls shorter than the server's session TTL
- Configure sticky sessions (or a shared session store) on load-balanced MCP servers
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
- MCP session expired: {error}
- MCP catalog changed after tool resolution; retry the call
- GET timeout
- MCP SSE rejected (transport=http url={} status={}): {}
- MCP session expired (transport=sse endpoint={} status={}): {
AI-assisted analysis of Hmbown/CodeWhale@8880682c63 (2026-08-16).
Data as JSON: /api/errors/8cd142b444096b68.
Report an issue: GitHub.