{"record":{"id":"97d595610aaf7774","repo":"can1357/oh-my-pi","slug":"http-response-status-resuming-mcp-sse-stream","errorCode":null,"errorMessage":"HTTP ${response.status} resuming MCP SSE stream: ${text}","messagePattern":"HTTP (.+?) resuming MCP SSE stream: (.+?)","errorType":"http","errorClass":"SSEResumeError","httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/mcp/transports/http.ts","lineNumber":363,"sourceCode":"\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}\n\t\tif (!response.ok) {\n\t\t\tconst text = await response.text().catch(() => \"\");\n\t\t\tthrow new SSEResumeError(`HTTP ${response.status} resuming MCP SSE stream: ${text}`);\n\t\t}\n\t\tconst contentType = response.headers.get(\"Content-Type\") ?? \"\";\n\t\tif (!contentType.includes(\"text/event-stream\") || !response.body) {\n\t\t\tawait response.body?.cancel();\n\t\t\tthrow new SSEResumeError(`MCP SSE resume returned unsupported Content-Type: ${contentType || \"(missing)\"}`);\n\t\t}\n\t\treturn response;\n\t}\n\n\t/** Route an SSE message (or batch) to the appropriate handler. */\n\t#dispatchSSEMessage(message: JsonRpcMessage | JsonRpcMessage[]): void {\n\t\tif (Array.isArray(message)) {\n\t\t\tfor (const m of message) this.#dispatchSSEMessage(m);\n\t\t\treturn;\n\t\t}\n\t\t// Server-to-client request: has both method and id\n\t\tif (\"method\" in message && \"id\" in message && message.id != null) {\n\t\t\tvoid this.#handleServerRequest(message as JsonRpcRequest);","sourceCodeStart":345,"sourceCodeEnd":381,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/mcp/transports/http.ts#L345-L381","documentation":"While resuming an MCP SSE stream, the server answered the GET with a non-2xx status. The transport reads the response body (error text) and throws SSEResumeError with the status and body, ending the logical stream rather than replaying the original POST.","triggerScenarios":"The SSE resume GET (Accept: text/event-stream, Last-Event-ID, Mcp-Session-Id) returns any non-ok status — e.g. 404 because the session expired/was evicted, 400 for an unrecognized Last-Event-ID, 5xx server error.","commonSituations":"Server restarted and lost session state (Mcp-Session-Id no longer valid); session TTL expired during idle; server version upgrade cleared event history; wrong Last-Event-ID format after a server change.","solutions":["Check the status and body in the message to identify the cause (404 → session gone, 4xx → bad resume state)","Reconnect the transport from scratch to establish a new session instead of resuming","If 404s recur after server restarts, configure the server to persist sessions or shorten client idle gaps","Verify client and server MCP protocol versions agree on session and Last-Event-ID semantics"],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"transport.onError = (err) => {\n  if (err instanceof SSEResumeError && /HTTP \\d+ resuming MCP SSE stream/.test(err.message)) {\n    // non-2xx on resume: session likely gone — do a full reconnect\n    scheduleReconnect();\n  }\n};","preventionTips":["Reconnect fully after server restarts; sessions rarely survive them","Keep the transport active or reconnect before session TTL expiry","Don't replay in-flight POSTs on resume failure (double-execution risk)","Read the embedded HTTP status/body to pick reconnect vs. backoff"],"tags":["mcp","sse","http","session","resume"],"backgroundTag":"sse-resume-failed","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}