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

  1. 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.
  2. 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.
  3. Rebuild and restart the worker (npm run build-and-sync or worker:start) so the /stream route and its handlers are current.
  4. If the status is 5xx, inspect worker logs for the upstream exception that produced the error response.
  5. 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

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


AI-assisted analysis of thedotmack/claude-mem@d768ba3643 (2026-08-12). Data as JSON: /api/errors/5fcd2ed79af9b1f5. Report an issue: GitHub.