Stirling-Tools/Stirling-PDF · error · Error

Response body is null

Error message

Response body is null

What it means

Thrown by consumeSSEStream when response.body is null/undefined. The Fetch API sets response.body to a ReadableStream only when the response is a streaming body; if the request was opaque (no-cors), a 204, or the body was already consumed, response.body is null and getReader() cannot be called.

Source

Thrown at frontend/editor/src/proprietary/components/chat/ChatContext.tsx:332

interface ProgressEvent {
  phase: string;
  timestamp: number;
  tool?: string;
  stepIndex?: number;
  stepCount?: number;
  engineDetail?: AnyEngineProgressDetail;
}

async function consumeSSEStream(
  response: Response,
  handlers: {
    onProgress: (data: ProgressEvent) => void;
    onResult: (data: AiWorkflowResponse) => void;
    onError: (data: { message: string }) => void;
  },
) {
  if (!response.body) {
    throw new Error("Response body is null");
  }
  const reader = response.body.getReader();
  const decoder = new TextDecoder();
  let buffer = "";
  let currentEvent = "";

  try {
    for (;;) {
      const { done, value } = await reader.read();
      if (done) break;
      buffer += decoder.decode(value, { stream: true });

      // SSE frames are separated by double newlines
      let boundary = buffer.indexOf("\n\n");
      while (boundary !== -1) {
        const frame = buffer.slice(0, boundary);
        buffer = buffer.slice(boundary + 2);

View on GitHub (pinned to 9ef20dcab8)

Solutions

  1. Check response.ok and content-type (text/event-stream) before calling consumeSSEStream; handle non-streaming responses separately.
  2. Verify the backend endpoint actually returns a streaming body (not buffered by a proxy) — set proxy_buffering off and X-Accel-Buffering: no.
  3. Ensure no upstream code reads response.body or calls response.json() before this function.
  4. Provide a fallback error message when body is null instead of a raw throw.

Example fix

// before
if (!response.body) {
  throw new Error("Response body is null");
}

// after
if (!response.body) {
  throw new Error(
    `AI engine returned an empty stream (status ${response.status}). The engine may be unavailable or a proxy may have buffered the response.`,
  );
}
Defensive patterns

Strategy: validation

Validate before calling

// Validate the response is a stream before consuming
if (!response.ok || !response.body) {
  throw new Error(`AI engine returned no stream body (status ${response.status}).`);
}
const contentType = response.headers.get("content-type") ?? "";
if (!contentType.includes("event-stream")) {
  throw new Error(`Expected text/event-stream, got ${contentType}.`);
}

Type guard

function isStreamingResponse(res: Response): boolean {
  return !!res.body && (res.headers.get("content-type") ?? "").includes("event-stream");
}

Prevention

When it happens

Trigger: The AI orchestration endpoint returned a response with no body (e.g. 204 No Content, or a body already read by prior code), the fetch used a mode that yields an opaque response, or a service worker/proxy stripped the streaming body.

Common situations: The AI engine returned an empty 200/204 body due to an internal error. A misconfigured reverse proxy (nginx) buffered or dropped the streaming body. The response.json() was already awaited upstream, exhausting the body before consumeSSEStream is called.

Related errors


AI-assisted analysis of Stirling-Tools/Stirling-PDF@9ef20dcab8 (2026-08-13). Data as JSON: /api/errors/136692f87a1b6a3f. Report an issue: GitHub.