can1357/oh-my-pi · error · SSEResumeError
HTTP ${response.status} resuming MCP SSE stream: ${text}
Error message
HTTP ${response.status} resuming MCP SSE stream: ${text} What it means
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.
Source
Thrown at packages/coding-agent/src/mcp/transports/http.ts:363
const generated: Record<string, string> = {
Accept: "text/event-stream",
"Last-Event-ID": resume.lastEventId,
};
if (this.#sessionId) generated["Mcp-Session-Id"] = this.#sessionId;
let response = await this.#fetch({ method: "GET", signal }, generated);
if (this.onAuthError && (response.status === 401 || response.status === 403)) {
await response.body?.cancel();
const newHeaders = await this.onAuthError();
if (!newHeaders) {
throw new SSEResumeError(`HTTP ${response.status} resuming MCP SSE stream: auth refresh failed`);
}
// Persist refreshed headers so subsequent requests use them directly
this.config = { ...this.config, headers: newHeaders };
response = await this.#fetch({ method: "GET", signal }, generated);
}
if (!response.ok) {
const text = await response.text().catch(() => "");
throw new SSEResumeError(`HTTP ${response.status} resuming MCP SSE stream: ${text}`);
}
const contentType = response.headers.get("Content-Type") ?? "";
if (!contentType.includes("text/event-stream") || !response.body) {
await response.body?.cancel();
throw new SSEResumeError(`MCP SSE resume returned unsupported Content-Type: ${contentType || "(missing)"}`);
}
return response;
}
/** Route an SSE message (or batch) to the appropriate handler. */
#dispatchSSEMessage(message: JsonRpcMessage | JsonRpcMessage[]): void {
if (Array.isArray(message)) {
for (const m of message) this.#dispatchSSEMessage(m);
return;
}
// Server-to-client request: has both method and id
if ("method" in message && "id" in message && message.id != null) {
void this.#handleServerRequest(message as JsonRpcRequest);View on GitHub (pinned to 9690622007)
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
Defensive patterns
Strategy: try-catch
Try / catch
transport.onError = (err) => {
if (err instanceof SSEResumeError && /HTTP \d+ resuming MCP SSE stream/.test(err.message)) {
// non-2xx on resume: session likely gone — do a full reconnect
scheduleReconnect();
}
}; Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- MCP SSE resume returned unsupported Content-Type: ${contentT
- SSE response did not include a body
- No response received for request ID ${expectedId}
- MCP request failed: ${response.status} ${response.statusText
- MCP OAuth refresh failed: ${response.status} ${text}
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/97d595610aaf7774.
Report an issue: GitHub.