star7th/showdoc · error · Error

Response body is empty, server did not return a stream. Plea

Error message

Response body is empty, server did not return a stream. Please try again later.

What it means

Thrown by sendAgentMessage() when the response to POST /api/agent/agent had an OK status but res.body (a ReadableStream) is null/undefined, so there is nothing to getReader() on and the SSE loop cannot run. fetch() returns a null body for body-less responses (204 No Content, 304) and in environments without streaming response support (very old browsers, some test setups/polyfills, or a service worker returning a synthetic Response built without a stream).

Source

Thrown at web_src/src/api/aiAgent.ts:275

    let reader: ReadableStreamDefaultReader<any> | null = null
    try {
      const res = await fetch(url, {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          Accept: 'text/event-stream',
          'Cache-Control': 'no-cache',
        },
        body: JSON.stringify(body),
        signal: controller.signal,
      })

      if (!res.ok) {
        throw new Error(`HTTP ${res.status}: ${res.statusText}`)
      }

      if (!res.body) {
        throw new Error('Response body is empty, server did not return a stream. Please try again later.')
      }

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

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

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

        // SSE 按双换行分割
        const parts = buffer.split('\n\n')
        buffer = parts.pop() || ''

        for (const part of parts) {
          if (!part.trim()) continue

View on GitHub (pinned to 6a3fa91eee)

Solutions

  1. Reproduce with curl: `curl -N -X POST <host>/api/agent/agent -H 'Content-Type: application/json' -d '{...}'` and check whether any bytes stream back; if empty, debug the server route (PHP error log) rather than the client.
  2. If a service worker is registered, bypass it for this request (`fetch(url, { ...opts })` inside a `navigator.serviceWorker.getRegistrations()` check) or make it pass the stream through untouched.
  3. Confirm the deployment's proxy does not rewrite the response into an empty 200 (disable response buffering/compression for text/event-stream).
  4. For old-webview support, feature-detect res.body and fall back to res.text() parsing instead of throwing immediately.
  5. If it is transient (server restart mid-request), the built-in advice applies: retry after a short delay.

Example fix

// before
if (!res.body) {
  throw new Error('Response body is empty, server did not return a stream. Please try again later.')
}
reader = res.body.getReader()

// after
if (!res.body) {
  const text = await res.text()
  if (text) {
    // non-streaming fallback: parse the whole payload at once
    for (const part of text.split('\n\n')) params.onEvent(JSON.parse(part.replace(/^data:\s*/m, '')))
    params.onDone()
    return
  }
  throw new Error('Response body is empty, server did not return a stream. Please try again later.')
}
reader = res.body.getReader()
Defensive patterns

Strategy: fallback

Validate before calling

// capability check before opening the agent stream (once at app startup)
export const supportsStreamingResponse =
  typeof ReadableStream !== 'undefined' &&
  'body' in new Response('')

Type guard

function hasStreamBody(res: Response): res is Response & { body: ReadableStream<Uint8Array> } {
  return res.body instanceof ReadableStream
}

Try / catch

// existing structure already centralizes this: inside sendAgentMessage's IIFE catch,
// treat the empty-body error as retriable (server hiccup) but cap attempts:
} catch (e: any) {
  if (e?.message?.includes('Response body is empty') && attempt < 2) {
    attempt++
    await new Promise(r => setTimeout(r, 1000 * attempt))
    continue  // or re-invoke the IIFE
  }
  params.onError(e)
}

Prevention

When it happens

Trigger: Server (or an intermediate proxy) answering 200 with Content-Length: 0 or a 204 instead of the event-stream; PHP route echoing nothing before exit (e.g. fatal after SSE headers already sent as 200); a service worker or fetch polyfill intercepting the request and returning new Response() without a body; running the web_src app in an old webview where response.body is unsupported.

Common situations: A misconfigured proxy that swallows the streamed body but forwards the 200 status; a server code path that sends headers then dies before the first SSE chunk; error pages cached as empty 200s by an CDN/OPcache edge case; CI/component tests that mock fetch with a body-less Response.

Related errors


AI-assisted analysis of star7th/showdoc@6a3fa91eee (2026-08-21). Data as JSON: /api/errors/0b84582ff88fbdc1. Report an issue: GitHub.