danielmiessler/Fabric · error · Error
Response body is null
Error message
Response body is null
What it means
Thrown when api.stream() succeeds at the HTTP level but response.body is null/undefined, so getReader() cannot be called. In fetch, a response body is null only for opaque/filtered responses or when the body has already been consumed; a normal 200 with empty content still yields a non-null (immediately-done) stream.
Source
Thrown at web/src/lib/api/base.ts:47
get: <T>(endpoint: string) => api.fetch<T>(endpoint),
post: <T>(endpoint: string, data: unknown) => api.fetch<T>(endpoint, { method: 'POST', body: JSON.stringify(data) }),
put: <T>(endpoint: string, data?: unknown) => api.fetch<T>(endpoint, { method: 'PUT', body: data ? JSON.stringify(data) : undefined }),
delete: <T>(endpoint: string) => api.fetch<T>(endpoint, { method: 'DELETE' }),
stream: async function* (endpoint: string, data: unknown): AsyncGenerator<string> {
const response = await fetch(`/api${endpoint}`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data),
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const reader = response.body?.getReader();
if (!reader) throw new Error('Response body is null');
const decoder = new TextDecoder();
while (true) {
const { done, value } = await reader.read();
yield decoder.decode(value);
if (done) break;
}
}
};
export function createStorageAPI<T extends StorageEntity>(entityType: string) {
return {
async get(name: string): Promise<T> {
const response = await api.fetch<T>(`/${entityType}/${name}`);
if (response.error) throw new Error(response.error);
return response.data as T;
},View on GitHub (pinned to 338b89cfe9)
Solutions
- Ensure each stream() call issues a fresh fetch and never reuses a Response
- Confirm the request is same-origin (or CORS with proper headers), not mode:'no-cors'
- Verify the target browser supports fetch streaming (all evergreen browsers do; old Safari/IE do not)
Example fix
// before
const reader = response.body?.getReader();
if (!reader) throw new Error('Response body is null');
// after
if (!response.body) {
throw new Error(`Streaming not supported or body consumed (status ${response.status})`);
}
const reader = response.body.getReader(); Defensive patterns
Strategy: type-guard
Validate before calling
if (typeof ReadableStream === 'undefined' || typeof response?.body?.getReader !== 'function') {
// fall back to non-streaming JSON request
} Type guard
function hasStreamableBody(r: Response): r is Response & { body: ReadableStream<Uint8Array> } {
return r.body instanceof ReadableStream && typeof r.body.getReader === 'function';
} Try / catch
try { /* stream */ }
catch (e) {
if (e instanceof Error && e.message === 'Response body is null') {
// retry once with a fresh fetch, or degrade to non-streaming call
} else throw e;
} Prevention
- Never reuse a Response object across two reads
- Avoid no-cors mode for API calls that need streaming
- Feature-detect response.body support in environments with webviews
When it happens
Trigger: Calling api.stream() twice on the same Response object (body consumed the first time), a no-cors or otherwise opaque response where body is null, or an unusual runtime/polyfill that does not implement streaming bodies.
Common situations: Retrying logic that reuses a cached Response; running under an older browser or a fetch polyfill without ReadableStream support; a service worker returning an opaque response for /api routes.
Related errors
AI-assisted analysis of danielmiessler/Fabric@338b89cfe9 (2026-08-15).
Data as JSON: /api/errors/185f6d5e361dd8b7.
Report an issue: GitHub.