unslothai/unsloth · error · Error

Stream response missing body

Error message

Stream response missing body

What it means

Thrown when the /v1/chat/completions response is ok (2xx) but response.body is null, so there is no ReadableStream to read SSE events from. This guards the invariant that a streaming completions call must return a body. It fires before any chunk is parsed, so no partial transcript exists.

Source

Thrown at studio/frontend/src/features/chat/api/chat-api.ts:1317

export async function* streamChatCompletions(
  payload: OpenAIChatCompletionsRequest,
  signal: AbortSignal,
): AsyncGenerator<OpenAIChatChunk> {
  const response = await authFetch("/v1/chat/completions", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify(payload),
    signal,
  });

  if (!response.ok) {
    const body = await response.json().catch(() => null);
    throw new Error(parseErrorText(response.status, body));
  }

  if (!response.body) {
    throw new Error("Stream response missing body");
  }

  const reader = response.body.getReader();
  const decoder = new TextDecoder();
  let buffer = "";
  let completed = false;
  // EOF without `[DONE]` or a finish_reason chunk means the stream was cut mid-generation.
  let sawTerminalSignal = false;
  let terminalFinishReason: string | null = null;
  let sawAssistantContent = false;
  let sawReasoningContent = false;

  const throwIfReasoningOnlyLength = () => {
    if (
      terminalFinishReason === "length" &&
      sawReasoningContent &&
      !sawAssistantContent
    ) {

View on GitHub (pinned to 203007d190)

Solutions

  1. Check whether a proxy, service worker, or fetch shim sits between the app and the inference endpoint and strips response bodies.
  2. Verify the server actually sets Content-Type: text/event-stream and streams a body for this request.
  3. In tests, mock the Response with a real ReadableStream body.

Example fix

// before (broken test mock)
const res = new Response('', { status: 200 });

// after
const res = new Response(sseEncoder.encode('data: [DONE]\n\n'), { status: 200 });
Defensive patterns

Strategy: validation

Validate before calling

if (!response.body) {
  // detect stripped streams early: check content-type before reading
  const ct = response.headers.get('content-type') ?? '';
  if (!ct.includes('event-stream')) console.warn('non-SSE content-type:', ct);
}

Try / catch

try { yield* readStream(); } catch (e) { if (e.message === 'Stream response missing body') hintAtProxyIssue(); throw e; }

Prevention

When it happens

Trigger: A 2xx response with no body: an intermediary (proxy, service worker, mock fetch) stripping the stream; a server bug returning Content-Length: 0 with 200; a fetch polyfill or test double that omits body.

Common situations: Corporate proxies or middleware that buffer/empty streaming responses; service workers intercepting fetch; unit tests mocking fetch with new Response() lacking a body; HTTP/1.0 intermediaries that drop chunked encoding.

Related errors


AI-assisted analysis of unslothai/unsloth@203007d190 (2026-08-15). Data as JSON: /api/errors/b964e5dd77dc3a4c. Report an issue: GitHub.