janhq/jan · error

Response body is null

Error message

Response body is null

What it means

Thrown after the HTTP status check succeeds when the Response object has a null body, so the streaming reader cannot be created. In practice this means the runtime handed back a response with no readable stream, and the extension cannot iterate SSE chunks.

Solutions

  1. Check the response status/headers upstream — a null body with 200 usually means a proxy or adapter issue.
  2. Ensure you use the platform's standard fetch implementation (not an old polyfill) in the extension host.
  3. Retry the request; transient proxy failures can produce empty bodies.
  4. If it persists, log response.status and headers to identify which layer returned the empty response.

Example fix

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

Strategy: retry

Validate before calling

null

Type guard

function hasBody(res: Response): res is Response & { body: ReadableStream } {
  return res.body != null
}

Try / catch

let res = await fetch(url, init)
if (res.ok && !res.body) {
  res = await fetch(url, init) // retry once on empty body
}
if (!res.body) throw new Error('Empty response body after retry')

Prevention

When it happens

Trigger: A 2xx response whose body is null — typically from an unusual fetch polyfill/environment, an empty-body response from a proxy, or a runtime quirk in the host app's fetch implementation.

Common situations: Running inside an environment with a non-standard fetch (older polyfills, custom adapters in Node/Tauri hosts); middleware or proxies stripping the body on 204/empty responses.

Related errors


AI-assisted analysis of janhq/jan@7205d770c1 (2026-09-17). Data as JSON: /api/errors/9c16c01a4d99bd53. Report an issue: GitHub.

Appendix: source

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

    }
    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 7205d770c1)