{"record":{"id":"30cdf2d8a8e501d1","repo":"openai/codex","slug":"streamable-http-session-expired-with-404-not-found","errorCode":null,"errorMessage":"streamable HTTP session expired with 404 Not Found","messagePattern":"streamable HTTP session expired with 404 Not Found","errorType":"exception","errorClass":"StreamableHttpClientAdapterError","httpStatus":404,"severity":"error","filePath":"codex-rs/rmcp-client/src/http_client_adapter.rs","lineNumber":106,"sourceCode":"}\n\nstruct EventStreamCancellation {\n    request_id: RequestId,\n    cancellations: Arc<Mutex<HashMap<RequestId, oneshot::Sender<()>>>>,\n}\n\nimpl Drop for EventStreamCancellation {\n    fn drop(&mut self) {\n        self.cancellations\n            .lock()\n            .unwrap_or_else(PoisonError::into_inner)\n            .remove(&self.request_id);\n    }\n}\n\n#[derive(Debug, thiserror::Error)]\npub(crate) enum StreamableHttpClientAdapterError {\n    #[error(\"streamable HTTP session expired with 404 Not Found\")]\n    SessionExpired404,\n    #[error(transparent)]\n    HttpRequest(#[from] ExecServerError),\n    #[error(\"invalid HTTP header: {0}\")]\n    Header(String),\n    #[error(\"MCP response body exceeds {maximum_bytes} bytes\")]\n    ResponseTooLarge { maximum_bytes: usize },\n}\n\nimpl StreamableHttpClientAdapter {\n    pub(crate) fn new(\n        http_client: Arc<dyn HttpClient>,\n        default_headers: HeaderMap,\n        auth_provider: Option<SharedAuthProvider>,\n        has_configured_headers: bool,\n        redirect_mode: StreamableHttpRedirectMode,\n        initialize_deadline: Arc<Mutex<Option<Instant>>>,\n    ) -> Self {","sourceCodeStart":88,"sourceCodeEnd":124,"githubUrl":"https://github.com/openai/codex/blob/339751715c64496cb86246bfb3935f40e309dd3d/codex-rs/rmcp-client/src/http_client_adapter.rs#L88-L124","documentation":"In the MCP Streamable HTTP transport the server hands out an Mcp-Session-Id after initialize; when a request carrying that id gets 404 Not Found (POST at http_client_adapter.rs:275, GET event stream at :562), the server no longer knows the session. The adapter maps that specifically to SessionExpired404 so rmcp can distinguish an expired session from a generic HTTP failure and re-run the initialize handshake instead of failing blindly.","triggerScenarios":"post_message() with a session_id receiving HTTP 404, or get_stream() receiving 404 — typically after the server's session TTL elapses, the server restarts, or the session was never stored on the node that answered.","commonSituations":"MCP server restart or redeploy under a long-lived client; idle sessions exceeding the server TTL; load balancers without sticky sessions routing to a different instance; resuming a client after laptop sleep/hibernate; servers that persist sessions in memory only.","solutions":["Catch the error and re-run the MCP initialize handshake to obtain a fresh session id, then retry the request once","If you deploy the server, enable sticky sessions or share session state across instances","Check/increase the server's session TTL if clients legitimately stay idle","Emit periodic pings/keepalives so sessions do not expire during idle periods"],"exampleFix":"// before\nlet result = client.call_tool(\"search\", args).await?; // SessionExpired404 bubbles\n\n// after\nmatch client.call_tool(\"search\", args).await {\n    Ok(result) => Ok(result),\n    Err(e) if is_session_expired(&e) => {\n        let client = reinitialize_mcp_http_client(&config).await?;\n        client.call_tool(\"search\", args).await\n    }\n    Err(e) => Err(e),\n}","handlingStrategy":"retry","validationCode":null,"typeGuard":"fn is_session_expired(error: &rmcp::service::ServiceError) -> bool {\n    error.to_string().contains(\"session expired with 404\")\n}","tryCatchPattern":"// 404 on a known session id: re-initialize once, then retry\nmatch client.call_tool(name, args).await {\n    Ok(result) => Ok(result),\n    Err(e) if e.to_string().contains(\"session expired with 404\") => {\n        let client = reinitialize_mcp_http_client(&config).await?;\n        client.call_tool(name, args).await\n    }\n    Err(e) => Err(e),\n}","preventionTips":["Send periodic pings so server session TTLs never expire during idle","Use sticky sessions when load-balancing multiple MCP server instances","Re-initialize proactively after long suspensions (sleep/hibernate)","Cap retries at one re-initialization to avoid loops against a broken server"],"tags":["rust","mcp","streamable-http","http-404","session-expired"],"backgroundTag":"session-expired","analyzedSha":"339751715c64496cb86246bfb3935f40e309dd3d","analyzedAt":"2026-08-25T05:35:09.876Z","schemaVersion":2},"datasetVersion":"2026-08-25T06:17:31.827Z"}