can1357/oh-my-pi · error · SSEResumeError
HTTP ${response.status} resuming MCP SSE stream: auth refres
Error message
HTTP ${response.status} resuming MCP SSE stream: auth refresh failed What it means
While resuming an MCP SSE stream (GET with Last-Event-ID), the server returned 401/403 and the registered onAuthError callback returned no new headers — meaning auth refresh failed or was declined. The transport throws SSEResumeError so the pending POST is never replayed (the server may have already executed it).
Source
Thrown at packages/coding-agent/src/mcp/transports/http.ts:355
* Failures throw {@link SSEResumeError} so `request()` never replays the
* originating POST in response.
*/
async #fetchSSEResume(resume: SSEResumeState, signal: AbortSignal): Promise<Response> {
if (resume.lastEventId === null) {
throw new SSEResumeError("SSE stream ended without a resumable event ID");
}
await waitForSSERetry(resume.retryMs, signal);
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. */View on GitHub (pinned to 9690622007)
Solutions
- Re-authenticate and reconnect the MCP transport with fresh credentials
- Verify the auth provider's refresh flow works (check refresh token validity/expiry)
- Increase token TTL or implement proactive refresh before expiry so streams don't die mid-session
- Confirm the credentials' audience/scopes match the MCP server's requirements
Example fix
// before
onAuthError: async () => expiredRefreshToken ? refresh(expiredRefreshToken) : undefined
// after
onAuthError: async () => {
const refreshed = await refreshWithRetry(refreshToken); // handles transient refresh failures
return refreshed ? { Authorization: `Bearer ${refreshed.accessToken}` } : undefined;
} Defensive patterns
Strategy: fallback
Validate before calling
// Verify credentials are refreshable before starting long-lived streams:
if (!refreshToken) throw new Error("cannot maintain MCP streams without a refreshable credential"); Try / catch
try {
await transport.request(method, params);
} catch (err) {
if (err instanceof SSEResumeError && err.message.includes("auth refresh failed")) {
await reauthenticateAndReconnect(); // do NOT replay the request
} else throw err;
} Prevention
- Wire a robust onAuthError that actually returns fresh headers
- Refresh tokens proactively before expiry, not only on 401
- Never replay the original POST after this error — the server may have executed it
- Validate refresh-token validity at connect time
When it happens
Trigger: SSE resume GET returns 401 or 403 with onAuthError wired, and the callback resolves to undefined/null (refresh flow exhausted, refresh token invalid, user declined re-auth).
Common situations: OAuth access token expired mid-stream and the refresh token is revoked; auth provider outage during token refresh; credentials configured for the wrong audience; long-lived MCP sessions outlasting token TTLs.
Related errors
- No OAuth credential available for provider: ${provider}
- MCP OAuth credential is missing refresh material
- Broker returned non-OAuth credential for ${provider}
- Token exchange returned no access token${providerError ? `:
- MCP OAuth refresh failed: ${response.status} ${text}
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/f57ce9d98c6002da.
Report an issue: GitHub.