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

  1. Check the status and body in the message to identify the cause (404 → session gone, 4xx → bad resume state)
  2. Reconnect the transport from scratch to establish a new session instead of resuming
  3. If 404s recur after server restarts, configure the server to persist sessions or shorten client idle gaps
  4. 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

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


AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31). Data as JSON: /api/errors/97d595610aaf7774. Report an issue: GitHub.