janhq/jan · error · Error

Response body is null

Error message

Response body is null

What it means

Thrown by handleStreamingResponse() right after a successful (OK) fetch, when response.body is null. The HTTP layer returned a 2xx but no readable stream body, so SSE parsing cannot proceed. This is a runtime/transport anomaly rather than a server rejection.

Source

Thrown at extensions/mlx-extension/src/index.ts:456

        )
      }
    }
    const response = await fetch(url, {
      method: 'POST',
      headers,
      body,
      signal: combinedController.signal,
    }).finally(() => clearTimeout(timeoutId))

    if (!response.ok) {
      const errorData = await response.json().catch(() => null)
      throw new Error(
        `MLX API request failed with status ${response.status}: ${JSON.stringify(errorData)}`
      )
    }

    if (!response.body) {
      throw new Error('Response body is null')
    }

    const reader = response.body.getReader()
    const decoder = new TextDecoder('utf-8')
    let buffer = ''

    try {
      while (true) {
        const { done, value } = await reader.read()
        if (done) break

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

        const lines = buffer.split('\n')
        buffer = lines.pop() || ''

        for (const line of lines) {
          const trimmedLine = line.trim()

View on GitHub (pinned to fad3f12a14)

Solutions

  1. Ensure the fetch runs in an environment with full ReadableStream support (not an opaque response).
  2. Avoid consuming response.body/json before reaching the stream reader.
  3. If the runtime lacks body, fall back to non-streaming chat and parse the full JSON.
  4. Check that no proxy intercepts and re-wraps the response without a body.

Example fix

// before
if (!response.body) throw new Error('Response body is null')
const reader = response.body.getReader()

// after
if (!response.body) {
  logger.warn('Streaming body null; falling back to non-stream parse')
  const data = (await response.json()) as chatCompletion
  return (async function* () { yield toChunk(data) })()
}
const reader = response.body.getReader()
Defensive patterns

Strategy: fallback

Validate before calling

// verify runtime exposes streaming bodies before requesting stream
const supportsBody = typeof Response !== 'undefined' && 'body' in Response.prototype
if (!supportsBody) opts.stream = false // request non-streaming instead

Type guard

function hasReadableBody(r: Response): r is Response & { body: ReadableStream<Uint8Array> } {
  return r.body != null && typeof (r.body as any).getReader === 'function'
}

Try / catch

try {
  return yield* engine.chat({ ...opts, stream: true }, abort)
} catch (e) {
  if (/Response body is null/.test(String(e))) {
    // fall back to non-streaming
    const full = (await engine.chat({ ...opts, stream: false }, abort)) as chatCompletion
    return full
  }
  throw e
}

Prevention

When it happens

Trigger: An opaque response (type 'opaque'/'opaqueredirect') whose body is null; a fetch polyfill or environment (some WebKit/JSC builds) that does not expose body for streaming; a misconfigured fetch that consumed the body before this check; a response with Content-Length but no body stream.

Common situations: Running under a runtime where Response.body is unsupported; a service worker/proxy returning an empty body; the body was accidentally read elsewhere; CORS opacity stripping the body.

Related errors


AI-assisted analysis of janhq/jan@fad3f12a14 (2026-08-12). Data as JSON: /api/errors/9bfdfde7654bc8df. Report an issue: GitHub.