Stirling-Tools/Stirling-PDF · error · Error
Response body is null
Error message
Response body is null
What it means
Thrown by consumeSSEStream when response.body is null/undefined. The Fetch API sets response.body to a ReadableStream only when the response is a streaming body; if the request was opaque (no-cors), a 204, or the body was already consumed, response.body is null and getReader() cannot be called.
Source
Thrown at frontend/editor/src/proprietary/components/chat/ChatContext.tsx:332
interface ProgressEvent {
phase: string;
timestamp: number;
tool?: string;
stepIndex?: number;
stepCount?: number;
engineDetail?: AnyEngineProgressDetail;
}
async function consumeSSEStream(
response: Response,
handlers: {
onProgress: (data: ProgressEvent) => void;
onResult: (data: AiWorkflowResponse) => void;
onError: (data: { message: string }) => void;
},
) {
if (!response.body) {
throw new Error("Response body is null");
}
const reader = response.body.getReader();
const decoder = new TextDecoder();
let buffer = "";
let currentEvent = "";
try {
for (;;) {
const { done, value } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
// SSE frames are separated by double newlines
let boundary = buffer.indexOf("\n\n");
while (boundary !== -1) {
const frame = buffer.slice(0, boundary);
buffer = buffer.slice(boundary + 2);
View on GitHub (pinned to 9ef20dcab8)
Solutions
- Check response.ok and content-type (text/event-stream) before calling consumeSSEStream; handle non-streaming responses separately.
- Verify the backend endpoint actually returns a streaming body (not buffered by a proxy) — set proxy_buffering off and X-Accel-Buffering: no.
- Ensure no upstream code reads response.body or calls response.json() before this function.
- Provide a fallback error message when body is null instead of a raw throw.
Example fix
// before
if (!response.body) {
throw new Error("Response body is null");
}
// after
if (!response.body) {
throw new Error(
`AI engine returned an empty stream (status ${response.status}). The engine may be unavailable or a proxy may have buffered the response.`,
);
} Defensive patterns
Strategy: validation
Validate before calling
// Validate the response is a stream before consuming
if (!response.ok || !response.body) {
throw new Error(`AI engine returned no stream body (status ${response.status}).`);
}
const contentType = response.headers.get("content-type") ?? "";
if (!contentType.includes("event-stream")) {
throw new Error(`Expected text/event-stream, got ${contentType}.`);
} Type guard
function isStreamingResponse(res: Response): boolean {
return !!res.body && (res.headers.get("content-type") ?? "").includes("event-stream");
} Prevention
- Confirm the endpoint returns text/event-stream and that proxies do not buffer it.
- Never consume response.json() before handing the response to the stream reader.
- Surface a descriptive error including status and content-type.
When it happens
Trigger: The AI orchestration endpoint returned a response with no body (e.g. 204 No Content, or a body already read by prior code), the fetch used a mode that yields an opaque response, or a service worker/proxy stripped the streaming body.
Common situations: The AI engine returned an empty 200/204 body due to an internal error. A misconfigured reverse proxy (nginx) buffered or dropped the streaming body. The response.json() was already awaited upstream, exhausting the body before consumeSSEStream is called.
Related errors
- Stream ended without a result
- detail ?? `AI engine request failed: ${response.status}`
- OCR service error: ${title}
- Response is not a valid PDF. Header: "${head}"
- Missing stored file ID in response.
AI-assisted analysis of Stirling-Tools/Stirling-PDF@9ef20dcab8 (2026-08-13).
Data as JSON: /api/errors/136692f87a1b6a3f.
Report an issue: GitHub.