{"record":{"id":"ad2899a37628e3e6","repo":"can1357/oh-my-pi","slug":"sse-stream-ended-without-a-resumable-event-id","errorCode":null,"errorMessage":"SSE stream ended without a resumable event ID","messagePattern":"SSE stream ended without a resumable event ID","errorType":"exception","errorClass":"SSEResumeError","httpStatus":null,"severity":"warning","filePath":"packages/coding-agent/src/mcp/transports/http.ts","lineNumber":342,"sourceCode":"\t\t\t\t\tlogger.debug(\"HTTP SSE listener resume failed\", {\n\t\t\t\t\t\turl: this.config.url,\n\t\t\t\t\t\terror: error instanceof Error ? error.message : String(error),\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Resume a logical SSE stream via GET + `Last-Event-ID`, honoring the\n\t * server-provided retry interval and refreshing auth once on 401/403.\n\t * Failures throw {@link SSEResumeError} so `request()` never replays the\n\t * originating POST in response.\n\t */\n\tasync #fetchSSEResume(resume: SSEResumeState, signal: AbortSignal): Promise<Response> {\n\t\tif (resume.lastEventId === null) {\n\t\t\tthrow new SSEResumeError(\"SSE stream ended without a resumable event ID\");\n\t\t}\n\t\tawait waitForSSERetry(resume.retryMs, signal);\n\t\tconst generated: Record<string, string> = {\n\t\t\tAccept: \"text/event-stream\",\n\t\t\t\"Last-Event-ID\": resume.lastEventId,\n\t\t};\n\t\tif (this.#sessionId) generated[\"Mcp-Session-Id\"] = this.#sessionId;\n\t\tlet response = await this.#fetch({ method: \"GET\", signal }, generated);\n\t\tif (this.onAuthError && (response.status === 401 || response.status === 403)) {\n\t\t\tawait response.body?.cancel();\n\t\t\tconst newHeaders = await this.onAuthError();\n\t\t\tif (!newHeaders) {\n\t\t\t\tthrow new SSEResumeError(`HTTP ${response.status} resuming MCP SSE stream: auth refresh failed`);\n\t\t\t}\n\t\t\t// Persist refreshed headers so subsequent requests use them directly\n\t\t\tthis.config = { ...this.config, headers: newHeaders };\n\t\t\tresponse = await this.#fetch({ method: \"GET\", signal }, generated);\n\t\t}","sourceCodeStart":324,"sourceCodeEnd":360,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/mcp/transports/http.ts#L324-L360","documentation":"The long-lived GET SSE listener dropped without ever delivering an event carrying an `id:` field, so the transport cannot resume the logical stream with a Last-Event-ID header. It throws SSEResumeError to end the stream cleanly so `request()` never replays the original POST (which the server may have already executed).","triggerScenarios":"The physical SSE connection breaks (network drop, server restart) while resume.lastEventId is still null — i.e. the server sent no id-tagged events before the stream ended.","commonSituations":"Connecting to an MCP server that does not emit event IDs on its stream; a very short-lived connection that dies during initialization; a server that closes the GET stream immediately after responding.","solutions":["Ensure the MCP server includes `id:` fields on SSE events so resumption is possible (required for streamability per the MCP spec)","Treat this as a logical stream end: the transport's onClose/reconnect path handles it — verify server liveness and reconnect via normal connect flow","Check network stability between client and server; a proxy or idle timeout may be cutting the connection before events arrive"],"exampleFix":null,"handlingStrategy":"fallback","validationCode":null,"typeGuard":null,"tryCatchPattern":"transport.onError = (err) => {\n  if (err.message === \"SSE stream ended without a resumable event ID\") {\n    // logical stream ended; schedule a full reconnect instead of expecting resume\n    scheduleReconnect();\n  }\n};","preventionTips":["Ensure the MCP server emits id: fields on SSE events","Monitor connection stability; dropouts before the first event mean resume is impossible","Treat this as a reconnect signal, not a retryable request failure","Keep sessions short on flaky networks"],"tags":["mcp","sse","streaming","network"],"backgroundTag":"sse-stream-disconnected","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}