janhq/jan · error · Error

Response body is null

Error message

Response body is null

What it means

Thrown by handleStreamingResponse() when response.ok is true but response.body is null/undefined, so getReader() cannot proceed. Streaming inference requires a readable body; a 2xx with no body is a protocol violation by the server (or a runtime/proxy that swallowed the body). The check exists because reading a null body would throw a less informative TypeError further down.

Source

Thrown at extensions/llamacpp-extension/src/index.ts:3665

    }
    const response = await fetch(url, {
      method: 'POST',
      headers,
      body,
      connectTimeout: Number(this.timeout) * 1000, // default 10 minutes
      signal: combinedController.signal,
    }).finally(() => clearTimeout(timeoutId))
    if (!response.ok) {
      const errorData = await response.json().catch(() => null)
      throw new Error(
        `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 = ''
    let jsonStr = ''
    try {
      while (true) {
        const { done, value } = await reader.read()

        if (done) {
          break
        }

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

        // Process complete lines in the buffer
        const lines = buffer.split('\n')

View on GitHub (pinned to fad3f12a14)

Solutions

  1. If behind a proxy, disable buffering for the streaming route (e.g. nginx proxy_buffering off; add header X-Accel-Buffering: no).
  2. Ensure opts.stream=true actually reaches the router and the router version supports SSE streaming.
  3. Retry as a non-streaming request if the runtime/proxy cannot deliver a body.
  4. Check the router logs - a 200 with empty body often indicates an upstream handler bug.

Example fix

// before - proxy strips body
const s = await provider.chat({ ...opts, stream: true }, ac) // throws: body is null
// after - fall back to non-streaming
let s
try { s = await provider.chat({ ...opts, stream: true }, ac) }
catch (e) {
  if (/body is null/i.test(String(e))) { const r = await provider.chat({ ...opts, stream: false }, ac); /* use r */ return }
  throw e
}
Defensive patterns

Strategy: fallback

Validate before calling

// Probe whether streaming is deliverable in this environment (optional)
// Most callers cannot pre-check; instead catch and degrade to non-streaming.
const supportsStreamBody = typeof ReadableStream !== 'undefined'
if (!supportsStreamBody && opts.stream) { console.warn('streaming not supported - downgrading'); opts.stream = false }

Type guard

// No static type guard; runtime check is on the response.
function responseHasBody(r: Response): boolean { return r.body != null }

Try / catch

let result
try { result = await provider.chat({ ...opts, stream: true }, ac) }
catch (e) {
  if (/body is null/i.test(String(e))) { result = await provider.chat({ ...opts, stream: false }, ac) }
  else throw e
}

Prevention

When it happens

Trigger: A reverse proxy (nginx/cloudflare) buffered or stripped the stream and returned 200 with empty body. The runtime's fetch implementation does not expose body for this response type. The router returned 204 No Content by mistake. A misconfigured middleware compressed/absorbed the SSE stream.

Common situations: Self-hosted behind a buffering proxy without proxy_buffering off / X-Accel-Buffering. WebKit/JavaScriptCore fetch quirks. Router version mismatch returning a non-streaming 200. Streaming was requested (opts.stream=true) but the upstream silently downgraded.

Related errors


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