{"record":{"id":"cb8b89e195f1d642","repo":"spring-projects/spring-ai","slug":"failed-to-complete-sse-builder-for-session-this","errorCode":null,"errorMessage":"Failed to complete SSE builder for session + this.sessionId + : + e.getMessage()","messagePattern":"Failed to complete SSE builder for session \\+ this\\.sessionId \\+ : \\+ e\\.getMessage\\(\\)","errorType":"console","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"mcp/transport/mcp-spring-webmvc/src/main/java/org/springframework/ai/mcp/server/webmvc/transport/WebMvcStreamableServerTransportProvider.java","lineNumber":837,"sourceCode":"\t\t\tthis.lock.lock();\n\t\t\ttry {\n\t\t\t\tif (this.closed) {\n\t\t\t\t\tif (logger.isDebugEnabled()) {\n\t\t\t\t\t\tlogger.debug(\"Session transport \" + this.sessionId + \" already closed\");\n\t\t\t\t\t}\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tthis.closed = true;\n\n\t\t\t\tthis.sseBuilder.complete();\n\t\t\t\tif (logger.isDebugEnabled()) {\n\t\t\t\t\tlogger.debug(\"Successfully completed SSE builder for session \" + this.sessionId);\n\t\t\t\t}\n\t\t\t}\n\t\t\tcatch (Exception e) {\n\t\t\t\tif (logger.isWarnEnabled()) {\n\t\t\t\t\tlogger.warn(\"Failed to complete SSE builder for session \" + this.sessionId + \": \" + e.getMessage());\n\t\t\t\t}\n\t\t\t}\n\t\t\tfinally {\n\t\t\t\tthis.lock.unlock();\n\t\t\t}\n\t\t}\n\n\t}\n\n\t/**\n\t * Builder for creating instances of {@link WebMvcStreamableServerTransportProvider}.\n\t */\n\tpublic static class Builder {\n\n\t\tprivate @Nullable McpJsonMapper jsonMapper;\n\n\t\tprivate String mcpEndpoint = \"/mcp\";\n","sourceCodeStart":819,"sourceCodeEnd":855,"githubUrl":"https://github.com/spring-projects/spring-ai/blob/98a7beda4f29d80a71c5837eb4053b03a93a46f7/mcp/transport/mcp-spring-webmvc/src/main/java/org/springframework/ai/mcp/server/webmvc/transport/WebMvcStreamableServerTransportProvider.java#L819-L855","documentation":"WebMvcStreamableServerTransportProvider.close() completes the SSE emitter for a streamable HTTP session under a lock. If completion throws (client disconnected, response committed/timed out), it logs this warning with the session id and swallows the exception. It means the stream for that session was not closed gracefully.","triggerScenarios":"Closing a streamable-HTTP MCP session whose SseEmitter was already completed by the client disconnecting, an async timeout, or a previous close() call; the session id is included in the message.","commonSituations":"MCP clients dropping connections mid-conversation; HTTP keep-alive/proxy timeouts killing the stream; duplicate session teardown (e.g. both a DELETE handler and a shutdown hook closing the same session); container restarts during load testing.","solutions":["Check the logged sessionId: if the client disconnected first, this warning is benign and can be ignored or logged at debug level","Remove duplicate close() invocations for the same session (e.g. HttpDELETE handler plus shutdown cleanup) so the emitter is completed once","Tune proxy/load-balancer idle timeouts to exceed your MCP session lifetime","Raise spring.mvc.async.request-timeout so the container doesn't complete the response before the provider"],"exampleFix":"// before: both delete-session endpoint and shutdown hook close the session\nsessionFactory.onDelete(req -> provider.close(sessionId));\n// shutdown: provider.close(sessionId); // second close -> warning\n\n// after: close idempotently via a session registry\nsessions.remove(sessionId).ifPresent(id -> provider.close(id));","handlingStrategy":"try-catch","validationCode":"if (sessions.isActive(sessionId)) { sessions.close(sessionId); }","typeGuard":"boolean sessionExists(String sessionId) { return sessionRegistry.containsKey(sessionId); }","tryCatchPattern":"try {\n    provider.close(sessionId);\n} catch (Exception e) {\n    logger.debug(\"Stream for session {} already completed\", sessionId, e);\n}","preventionTips":["Centralize session teardown in one code path to avoid double close","Keep proxy/LB idle timeouts longer than the longest expected MCP session","Raise spring.mvc.async.request-timeout appropriately","Log sessionId on cleanup so warnings can be traced to client disconnects"],"tags":["sse","mcp","streamable-http","client-disconnect"],"backgroundTag":"broken-pipe","analyzedSha":"98a7beda4f29d80a71c5837eb4053b03a93a46f7","analyzedAt":"2026-09-11T14:15:49.441Z","contentChangedAt":"2026-09-11T14:15:49.441Z","schemaVersion":2},"datasetVersion":"2026-09-14T11:17:12.474Z"}