thedotmack/claude-mem · warning · Error
SSE stream returned HTTP ${response.status}
Error message
SSE stream returned HTTP ${response.status} What it means
The OpenClaw plugin's SSE client throws this when the worker's /stream endpoint responds with a non-2xx HTTP status (response.ok is false). It is the first guard in connectToSSEStream before the response body is read. The throw is caught immediately at index.ts:602, logged at warn level, and the loop reconnects with exponential backoff (1s doubling to 30s max), so it is a recoverable connection failure, not a fatal one.
Source
Thrown at openclaw/src/index.ts:550
setConnectionState: (state: ConnectionState) => void,
getSourceLabel: (project: string | null | undefined) => string,
botToken?: string
): Promise<void> {
let backoffMs = 1000;
const maxBackoffMs = 30000;
while (!abortController.signal.aborted) {
try {
setConnectionState("reconnecting");
api.logger.info(`[claude-mem] Connecting to SSE stream at ${workerBaseUrl(port)}/stream`);
const response = await fetch(`${workerBaseUrl(port)}/stream`, {
signal: abortController.signal,
headers: { Accept: "text/event-stream" },
});
if (!response.ok) {
throw new Error(`SSE stream returned HTTP ${response.status}`);
}
if (!response.body) {
throw new Error("SSE stream response has no body");
}
setConnectionState("connected");
backoffMs = 1000;
api.logger.info("[claude-mem] Connected to SSE stream");
const reader = response.body.getReader();
const decoder = new TextDecoder();
let buffer = "";
while (true) {
const { done, value } = await reader.read();
if (done) break;
View on GitHub (pinned to d768ba3643)
Solutions
- Confirm the worker is running and serving /stream: curl -i http://$CLAUDE_MEM_WORKER_HOST:$CLAUDE_MEM_WORKER_PORT/stream and expect HTTP 200 with content-type: text/event-stream.
- Verify workerPort plugin config matches the actual worker port (DEFAULT_WORKER_PORT from settings); check api.logger.info line just before the error which prints the exact URL attempted.
- Rebuild and restart the worker (npm run build-and-sync or worker:start) so the /stream route and its handlers are current.
- If the status is 5xx, inspect worker logs for the upstream exception that produced the error response.
- No code change is needed in the plugin — the loop already backs off and reconnects; only act if it never recovers (indicates a persistent worker-side problem).
Example fix
// before — no fix needed in the plugin; this error is already handled by the reconnect loop at index.ts:602-614
// to silence during intentional worker downtime, abort the controller:
// abortController.abort(); // breaks the while loop cleanly
// worker side (src/services/worker-service.ts) — ensure /stream is registered and returns 200:
// app.get('/stream', (req, res) => { res.setHeader('Content-Type','text/event-stream'); ... }) Defensive patterns
Strategy: retry
Validate before calling
// Before relying on the stream, confirm the worker endpoint answers 2xx:
async function workerStreamReachable(port: number, host: string): Promise<boolean> {
try {
const res = await fetch(`http://${host}:${port}/stream`, {
method: 'GET',
headers: { Accept: 'text/event-stream' },
});
return res.ok && res.body !== null;
} catch {
return false;
}
} Type guard
function isAbortReason(e: unknown, signal: AbortSignal): boolean {
return signal.aborted && e instanceof Error && e.name === 'AbortError';
} Try / catch
// The existing loop at openclaw/src/index.ts:602-614 is the recommended pattern: // catch -> if abort, break; else warn + exponential backoff (1s..30s) + retry. // No propagation to callers; treat as transient.
Prevention
- Pin workerPort to the actual worker port in plugin config to avoid hitting a stray service that returns non-SSE responses.
- Keep plugin and worker versions in sync so /stream always exists.
- Monitor the warn log '[claude-mem] SSE stream error' — persistent retries indicate a worker-side issue worth alerting on.
When it happens
Trigger: Calling GET ${workerBaseUrl(port)}/stream with Accept: text/event-stream and receiving any status outside 200-299 (e.g. 404 if the worker build lacks the /stream route, 503 if the worker is shutting down, 500 on an unhandled worker exception, or 401/403 if auth middleware is ever added). The check runs on every reconnect attempt inside the while(!aborted) loop.
Common situations: Worker not running or crashed on startup (connection refused surfaces as a network error, not this — this fires only when an HTTP response is actually returned). Wrong workerPort in plugin config pointing at a different service that returns non-SSE HTML (404/200-with-HTML can give 404). Worker restarted mid-stream and the first reconnect hits a briefly-not-ready server (503). Mismatched plugin/worker versions where the worker predates the /stream route.
Related errors
- SSE stream response has no body
- ${timeoutMessage} (timed out after ${timeoutMs}ms)
- Failed to get processing status: ${res.status}
- Failed to trigger processing: ${res.status}
- Worker request timed out after ${WORKER_FETCH_TIMEOUT_MS}ms:
AI-assisted analysis of thedotmack/claude-mem@d768ba3643 (2026-08-12).
Data as JSON: /api/errors/5fcd2ed79af9b1f5.
Report an issue: GitHub.