Hmbown/CodeWhale · error
MCP session expired (transport=sse endpoint={} status={}): {
Error message
MCP session expired (transport=sse endpoint={} status={}): {} What it means
A POST to the SSE message endpoint failed and the body matched the stale-session classifier: is_mcp_stale_session_body() (crates/tui/src/mcp/wire.rs) looks for 'session' plus 'expired' or 'invalid', case-insensitive. The server no longer recognizes the session ID — it restarted, expired the session, or the POST landed on an instance without it. The connection must be re-established and re-initialized.
Source
Thrown at crates/tui/src/mcp/sse.rs:299
with_default_mcp_http_headers(self.client.post(&endpoint), true),
&headers,
)
.body(msg)
.send()
.await
.with_context(|| {
format!(
"MCP SSE POST send failed (transport=sse endpoint={})",
mask_url_secrets(&endpoint)
)
})?;
let status = response.status();
if !status.is_success() {
let body_excerpt = bounded_body_excerpt(response, ERROR_BODY_PREVIEW_BYTES).await;
let stale_session = is_mcp_stale_session_body(&body_excerpt);
let body_excerpt = self.auth.server_error_preview(&body_excerpt);
if stale_session {
anyhow::bail!(
"MCP session expired (transport=sse endpoint={} status={}): {}",
mask_url_secrets(&endpoint),
status,
body_excerpt
);
}
anyhow::bail!(
"MCP SSE POST rejected (transport=sse endpoint={} status={}): {}",
mask_url_secrets(&endpoint),
status,
body_excerpt
);
}
Ok(())
}
async fn recv(&mut self) -> Result<Vec<u8>> {
loop {View on GitHub (pinned to 8880682c63)
Solutions
- Reconnect: tear down the transport and redo connect + initialize to obtain a fresh endpoint and session
- Rely on the client's stale-session reconnect handling (wire.rs is_mcp_stale_session_error) instead of retrying the same POST
- If frequent, increase the server's session TTL or add sticky sessions for the messages endpoint
Example fix
// before: retrying the same POST keeps failing
transport.send(request).await?;
// after: on stale session, reconnect and re-initialize
match transport.send(request).await {
Err(e) if is_mcp_stale_session_error(&e) => {
transport = reconnect(&config).await?; // fresh endpoint event + session
initialize(&mut transport).await?;
transport.send(request).await?
}
other => other?,
} Defensive patterns
Strategy: retry
Type guard
```rust
// crates/tui/src/mcp/wire.rs already ships this classifier:
// is_mcp_stale_session_error(&err) matches "MCP session expired" and friends.
fn is_stale_sse_session(err: &anyhow::Error) -> bool {
err.to_string().starts_with("MCP session expired (transport=sse")
}
``` Try / catch
```rust
match transport.send(payload).await {
Err(e) if is_stale_sse_session(&e) => {
transport = reconnect(&cfg).await?; // fresh endpoint event + session
initialize(&mut transport).await?; // re-run initialize handshake
transport.send(payload).await
}
other => other,
}
``` Prevention
- Keep client state re-initializable so reconnects across server deploys are cheap
- Use sticky sessions when running multiple MCP server instances behind a load balancer
When it happens
Trigger: The MCP SSE server restarts (deploy, crash) while the client holds an old session ID; server-side session TTL expiry on a long-lived connection; a load balancer without sticky sessions routing the POST to a different instance.
Common situations: Server upgraded mid-session; multi-instance deployments missing session affinity; idle connections outliving the server's session TTL.
Related errors
- MCP session expired: {error}
- MCP Streamable HTTP session expired; retry with a new sessio
- MCP SSE connect cancelled before authentication completed
- MCP SSE connect cancelled before the request completed
- MCP SSE rejected (transport=http url={} status={}): {}
AI-assisted analysis of Hmbown/CodeWhale@8880682c63 (2026-08-16).
Data as JSON: /api/errors/0bc57f2bc6db5b63.
Report an issue: GitHub.