{"record":{"id":"185f6d5e361dd8b7","repo":"danielmiessler/Fabric","slug":"response-body-is-null","errorCode":null,"errorMessage":"Response body is null","messagePattern":"Response body is null","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"web/src/lib/api/base.ts","lineNumber":47,"sourceCode":"\n  get: <T>(endpoint: string) => api.fetch<T>(endpoint),\n  post: <T>(endpoint: string, data: unknown) => api.fetch<T>(endpoint, { method: 'POST', body: JSON.stringify(data) }),\n  put: <T>(endpoint: string, data?: unknown) => api.fetch<T>(endpoint, { method: 'PUT', body: data ? JSON.stringify(data) : undefined }),\n  delete: <T>(endpoint: string) => api.fetch<T>(endpoint, { method: 'DELETE' }),\n\n  stream: async function* (endpoint: string, data: unknown): AsyncGenerator<string> {\n    const response = await fetch(`/api${endpoint}`, {\n      method: 'POST',\n      headers: { 'Content-Type': 'application/json' },\n      body: JSON.stringify(data),\n    });\n\n    if (!response.ok) {\n      throw new Error(`HTTP error! status: ${response.status}`);\n    }\n\n    const reader = response.body?.getReader();\n    if (!reader) throw new Error('Response body is null');\n\n    const decoder = new TextDecoder();\n    while (true) {\n      const { done, value } = await reader.read();\n      yield decoder.decode(value);\n\n      if (done) break;\n    }\n  }\n};\n\nexport function createStorageAPI<T extends StorageEntity>(entityType: string) {\n  return {\n    async get(name: string): Promise<T> {\n      const response = await api.fetch<T>(`/${entityType}/${name}`);\n      if (response.error) throw new Error(response.error);\n      return response.data as T;\n    },","sourceCodeStart":29,"sourceCodeEnd":65,"githubUrl":"https://github.com/danielmiessler/Fabric/blob/338b89cfe97ab2d12ce30ce8b5449857a841366d/web/src/lib/api/base.ts#L29-L65","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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)"],"exampleFix":"// before\nconst reader = response.body?.getReader();\nif (!reader) throw new Error('Response body is null');\n\n// after\nif (!response.body) {\n  throw new Error(`Streaming not supported or body consumed (status ${response.status})`);\n}\nconst reader = response.body.getReader();","handlingStrategy":"type-guard","validationCode":"if (typeof ReadableStream === 'undefined' || typeof response?.body?.getReader !== 'function') {\n  // fall back to non-streaming JSON request\n}","typeGuard":"function hasStreamableBody(r: Response): r is Response & { body: ReadableStream<Uint8Array> } {\n  return r.body instanceof ReadableStream && typeof r.body.getReader === 'function';\n}","tryCatchPattern":"try { /* stream */ }\ncatch (e) {\n  if (e instanceof Error && e.message === 'Response body is null') {\n    // retry once with a fresh fetch, or degrade to non-streaming call\n  } else throw e;\n}","preventionTips":["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"],"tags":["streaming","fetch","browser-support"],"backgroundTag":null,"analyzedSha":"338b89cfe97ab2d12ce30ce8b5449857a841366d","analyzedAt":"2026-08-15T11:38:51.759Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}