thedotmack/claude-mem · warning · Error

SSE stream response has no body

Error message

SSE stream response has no body

What it means

Thrown by the OpenClaw SSE client after the worker returns HTTP 2xx for /stream but response.body is null/undefined. fetch() in modern runtimes always provides a ReadableStream body for text/event-stream responses, so a missing body implies a stripped-down fetch polyfill, a non-streaming host environment, or an edge runtime that materialised a Response without a body. Like error [0], it is caught at index.ts:602 and triggers a backoff-and-reconnect cycle, so it is self-healing.

Source

Thrown at openclaw/src/index.ts:554

  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;

        buffer += decoder.decode(value, { stream: true });

        if (buffer.length > MAX_SSE_BUFFER_SIZE) {
          api.logger.warn("[claude-mem] SSE buffer overflow, clearing buffer");

View on GitHub (pinned to d768ba3643)

Solutions

  1. Run under Node >= 18 or a runtime with full Web Streams fetch support (the worker itself requires Bun; the plugin client should target a modern runtime too).
  2. Hit /stream directly with curl --no-buffer and confirm bytes flow incrementally, ruling out a proxy buffering the body.
  3. Check the worker route returns a genuine streaming response (sets Content-Type: text/event-stream and writes chunks with flush), not a closed response.
  4. If this fires from a test harness, ensure the fetch stub returns { ok:true, body: new ReadableStream(...) }.
  5. Rely on the existing backoff reconnect — it will succeed once a runtime with a real streaming body is available.

Example fix

// before
const response = await fetch(`${workerBaseUrl(port)}/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");

// guard additionally with a clear log distinguishing the runtime cause:
if (!response.body) {
  api.logger.warn(`[claude-mem] /stream returned 2xx with no body — fetch runtime may lack streaming support`);
  throw new Error("SSE stream response has no body");
}
Defensive patterns

Strategy: validation

Validate before calling

// Detect runtimes whose fetch doesn't expose streaming bodies before subscribing:
function fetchHasStreamingBody(): boolean {
  // feature-detect: undici/Node>=18 expose ReadableStream on Response
  return typeof ReadableStream !== 'undefined';
}

Type guard

function responseHasBody(res: Response): res is Response & { body: ReadableStream<Uint8Array> } {
  return res.body != null;
}

Try / catch

// Already handled by the connect loop's catch (index.ts:602) with backoff.
// If missing bodies are persistent in a given runtime, abort and surface:
if (!response.body) {
  api.logger.error('[claude-mem] fetch runtime lacks streaming bodies; disabling SSE feed');
  abortController.abort();
}

Prevention

When it happens

Trigger: A host runtime whose fetch returns a Response object with .body undefined (older Node without experimental fetch stream support, or an undici version that does not expose streaming bodies for certain content-types). A worker middleware that sends 200 with an empty/absent body before the SSE handler attaches. A proxy or test harness that buffers the entire response and presents it without a stream handle.

Common situations: Running the OpenClaw plugin under a Node version below 18 (no native fetch streaming) or in an environment that polyfills fetch incompletely. A reverse proxy in front of the worker that does not forward chunked/streamed bodies. Unit tests stubbing fetch with a Response lacking a body field.

Related errors


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