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
- If behind a proxy, disable buffering for the streaming route (e.g. nginx proxy_buffering off; add header X-Accel-Buffering: no).
- Ensure opts.stream=true actually reaches the router and the router version supports SSE streaming.
- Retry as a non-streaming request if the runtime/proxy cannot deliver a body.
- 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
- Disable proxy buffering on the streaming route (nginx: proxy_buffering off).
- Add X-Accel-Buffering: no to SSE responses.
- Provide a non-streaming fallback path in the client for environments that strip stream bodies.
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
- API request failed with status ${response.status}: ${JSON.st
- Malformed chunk
- Response body is null
- ${error.message}
- MLX API request failed with status ${response.status}: ${JSO
AI-assisted analysis of janhq/jan@fad3f12a14 (2026-08-12).
Data as JSON: /api/errors/9c16c01a4d99bd53.
Report an issue: GitHub.