Hmbown/CodeWhale · warning · anyhow
GET timeout
Error message
GET timeout
What it means
During the Streamable HTTP session preflight, the transport opens a GET (SSE) request to establish a session, wrapped in a 5-second `tokio::time::timeout`. The elapsed arm of the select maps to "GET timeout" — the server did not respond to the session-establishing GET within 5 seconds.
Solutions
- Retry the connection; transient latency often clears on a second attempt.
- Check server health and network latency to the MCP endpoint (curl the URL with a 5s budget).
- Reduce server cold-start time or move the MCP endpoint closer to the client; if the server never answers GET preflight, verify it supports Streamable HTTP sessions.
Example fix
// serverless function cold-start // before: direct connect to cold lambda URL // after: keep-alive ping or provisioned-concurrency on the MCP endpoint
Defensive patterns
Strategy: retry
Validate before calling
// preflight connectivity check before opening the MCP session
let ok = tokio::time::timeout(
std::time::Duration::from_secs(5),
reqwest::get(url),
).await;
if ok.is_err() { eprintln!("endpoint not responding within 5s"); } Try / catch
match connect_with_policy(...) {
Err(e) if e.to_string().contains("GET timeout") => {
// retry with backoff; alert if the endpoint is persistently slow
}
other => other?,
} Prevention
- Monitor MCP endpoint latency and cold-start times
- Avoid serverless cold starts for MCP servers (provisioned concurrency)
- Warn users when the endpoint is on a high-latency network path
When it happens
Trigger: Connecting to an MCP Streamable HTTP server whose GET preflight request takes longer than 5 seconds — slow or overloaded server, network latency, or a server that never opens the SSE stream for this endpoint.
Common situations: Remote MCP servers behind slow proxies or cold-starting serverless functions; corporate VPN latency; the server does not support the GET preflight and never responds.
Understand the failure class
Background: Request timed out: what client-side request timeouts mean across libraries (Request timed out, TIMED_OUT, APITimeoutError) — this error's family across 39 libraries.
- Timeouts: ETIMEDOUT, deadlines, and hung requests — what actually expires when a request times out.
Related errors
- building bundle fetch client failed
- bundle fetch failed with HTTP status
- bundle fetch request failed
- bundle redirects may not change URL scheme
- ${compactRuntimeError(response.status, body)}
AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22).
Data as JSON: /api/errors/15697e837198673b.
Report an issue: GitHub.
Appendix: source
Thrown at crates/tui/src/mcp/http.rs:221
let headers = tokio::select! {
biased;
_ = cancel.cancelled() => {
anyhow::bail!("MCP session preflight cancelled after plugin authority changed")
}
headers = transport.auth.resolved_headers() => headers?,
};
let request = apply_safe_custom_headers(
with_default_mcp_http_headers(transport.client.get(&transport.url), false),
&headers,
);
let response = tokio::select! {
biased;
_ = cancel.cancelled() => {
anyhow::bail!("MCP session preflight cancelled after plugin authority changed")
}
response = tokio::time::timeout(Duration::from_secs(5), transport.client.send(request)) => {
response
.map_err(|_| anyhow::anyhow!("GET timeout"))?
.map_err(|e| anyhow::anyhow!("GET error: {e}"))?
}
};
// Capture session ID from the GET response so subsequent POSTs
// (including `initialize`) can include it. This is the same
// header-reading logic that would be hit inside
// `StreamableHttpTransport::send` for POST responses, but since
// the GET is sent before any POST we do it here directly.
if let Some(sid) = response
.headers()
.get("Mcp-Session-Id")
.and_then(|v| v.to_str().ok())
&& transport.session_id.as_deref() != Some(sid)
{
let session_ref = crate::utils::redacted_identifier_for_log(sid);
tracing::debug!(target: "mcp", session = %session_ref, "captured MCP session ID via GET preflight");
transport.session_id = Some(sid.to_string());View on GitHub (pinned to 73e0f67d83)