{"record":{"id":"ffca0b68389eb67c","repo":"thedotmack/claude-mem","slug":"sse-stream-response-has-no-body","errorCode":null,"errorMessage":"SSE stream response has no body","messagePattern":"SSE stream response has no body","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"warning","filePath":"openclaw/src/index.ts","lineNumber":554,"sourceCode":"  let backoffMs = 1000;\n  const maxBackoffMs = 30000;\n\n  while (!abortController.signal.aborted) {\n    try {\n      setConnectionState(\"reconnecting\");\n      api.logger.info(`[claude-mem] Connecting to SSE stream at ${workerBaseUrl(port)}/stream`);\n\n      const response = await fetch(`${workerBaseUrl(port)}/stream`, {\n        signal: abortController.signal,\n        headers: { Accept: \"text/event-stream\" },\n      });\n\n      if (!response.ok) {\n        throw new Error(`SSE stream returned HTTP ${response.status}`);\n      }\n\n      if (!response.body) {\n        throw new Error(\"SSE stream response has no body\");\n      }\n\n      setConnectionState(\"connected\");\n      backoffMs = 1000;\n      api.logger.info(\"[claude-mem] Connected to SSE stream\");\n\n      const reader = response.body.getReader();\n      const decoder = new TextDecoder();\n      let buffer = \"\";\n\n      while (true) {\n        const { done, value } = await reader.read();\n        if (done) break;\n\n        buffer += decoder.decode(value, { stream: true });\n\n        if (buffer.length > MAX_SSE_BUFFER_SIZE) {\n          api.logger.warn(\"[claude-mem] SSE buffer overflow, clearing buffer\");","sourceCodeStart":536,"sourceCodeEnd":572,"githubUrl":"https://github.com/thedotmack/claude-mem/blob/d768ba364302d12b76e69e4f021f0bb1d2d50ed6/openclaw/src/index.ts#L536-L572","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["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).","Hit /stream directly with curl --no-buffer and confirm bytes flow incrementally, ruling out a proxy buffering the body.","Check the worker route returns a genuine streaming response (sets Content-Type: text/event-stream and writes chunks with flush), not a closed response.","If this fires from a test harness, ensure the fetch stub returns { ok:true, body: new ReadableStream(...) }.","Rely on the existing backoff reconnect — it will succeed once a runtime with a real streaming body is available."],"exampleFix":"// before\nconst response = await fetch(`${workerBaseUrl(port)}/stream`, { ... });\nif (!response.ok) throw new Error(`SSE stream returned HTTP ${response.status}`);\nif (!response.body) throw new Error(\"SSE stream response has no body\");\n\n// guard additionally with a clear log distinguishing the runtime cause:\nif (!response.body) {\n  api.logger.warn(`[claude-mem] /stream returned 2xx with no body — fetch runtime may lack streaming support`);\n  throw new Error(\"SSE stream response has no body\");\n}","handlingStrategy":"validation","validationCode":"// Detect runtimes whose fetch doesn't expose streaming bodies before subscribing:\nfunction fetchHasStreamingBody(): boolean {\n  // feature-detect: undici/Node>=18 expose ReadableStream on Response\n  return typeof ReadableStream !== 'undefined';\n}","typeGuard":"function responseHasBody(res: Response): res is Response & { body: ReadableStream<Uint8Array> } {\n  return res.body != null;\n}","tryCatchPattern":"// Already handled by the connect loop's catch (index.ts:602) with backoff.\n// If missing bodies are persistent in a given runtime, abort and surface:\nif (!response.body) {\n  api.logger.error('[claude-mem] fetch runtime lacks streaming bodies; disabling SSE feed');\n  abortController.abort();\n}","preventionTips":["Target Node >= 18 (or Bun) for the plugin host so fetch streams are real.","Don't put a buffering proxy in front of /stream — forward chunked encoding.","In tests, stub fetch with { ok:true, body: new ReadableStream({ start(c){ c.enqueue(...); } }) }."],"tags":["sse","runtime","fetch","openclaw","reconnect"],"backgroundTag":null,"analyzedSha":"d768ba364302d12b76e69e4f021f0bb1d2d50ed6","analyzedAt":"2026-08-12T23:52:55.241Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}