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()) continueView on GitHub (pinned to 6a3fa91eee)
Solutions
- 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.
- 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.
- Confirm the deployment's proxy does not rewrite the response into an empty 200 (disable response buffering/compression for text/event-stream).
- For old-webview support, feature-detect res.body and fall back to res.text() parsing instead of throwing immediately.
- 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
- Feature-detect response.body support once and route old webviews to a non-streaming endpoint or res.text() parsing.
- Verify the endpoint with curl -N during integration so empty 200s are caught before release.
- Keep service workers from synthesizing body-less Responses for /api/agent/ requests.
- Log Content-Type of the failed response; anything other than text/event-stream indicates a proxy or error page intercepting the stream.
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.