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

  1. Ensure each stream() call issues a fresh fetch and never reuses a Response
  2. Confirm the request is same-origin (or CORS with proper headers), not mode:'no-cors'
  3. 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

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.