{"record":{"id":"a22598cbd9ba4660","repo":"tursodatabase/turso","slug":"reader-is-null","errorCode":null,"errorMessage":"reader is null","messagePattern":"reader is null","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"bindings/javascript/sync/packages/common/run.ts","lineNumber":57,"sourceCode":"        url = normalizeUrl(url);\n        try {\n            let headers = typeof opts.headers === \"function\" ? await opts.headers() : opts.headers;\n            if (requestType.headers != null && requestType.headers.length > 0) {\n                headers = { ...headers };\n                for (let header of requestType.headers) {\n                    headers[header[0]] = header[1];\n                }\n            }\n            const fetchImpl = opts.fetch ?? fetch;\n            const response = await fetchImpl(`${url}${requestType.path}`, {\n                method: requestType.method,\n                headers: headers,\n                body: requestType.body ?? null,\n            });\n            completion.status(response.status);\n            const reader = response.body?.getReader();\n            if (reader == null) {\n                throw new Error(\"reader is null\");\n            }\n            while (true) {\n                const { done, value } = await reader.read();\n                if (done) {\n                    completion.done();\n                    break;\n                }\n                completion.pushBuffer(value);\n            }\n        } catch (error) {\n            completion.poison(`fetch error: ${error}`);\n        }\n    } else if (requestType.type == 'FullRead') {\n        try {\n            const metadata = await io.read(requestType.path);\n            if (metadata != null) {\n                completion.pushBuffer(metadata);\n            }","sourceCodeStart":39,"sourceCodeEnd":75,"githubUrl":"https://github.com/tursodatabase/turso/blob/6c7252267988c76e632af00a671e4b9788dfae13/bindings/javascript/sync/packages/common/run.ts#L39-L75","documentation":"The HTTP streaming runner (bindings/javascript/sync/packages/common/run.ts:57) reads the response body with response.body?.getReader() and requires a web ReadableStream reader; when response.body is null/undefined it throws 'reader is null' (the surrounding catch then poisons the completion). A null body means the fetch implementation or intermediary returned a Response without a streaming body - the sync protocol requires a stream, so it cannot proceed.","triggerScenarios":"Passing a custom opts.fetch (or environment default) whose Response has no body: fetch polyfills/mocks (msw/node older versions, jest mocks) constructing bare Response objects; HTTP 204/304 or gateway responses that legitimately carry no body; proxies or service workers stripping the body; runtimes where Response.body is undefined despite fetch existing.","commonSituations":"Unit tests with mocked fetch returning new Response() without a stream; corporate proxies or CDN edge errors returning empty-body statuses; Node 16-era undici/polyfill combos where body is a Node stream without getReader; server returning 304 due to conditional headers.","solutions":["Pass a fetch implementation that returns real streaming Responses (native fetch / modern undici) via the opts.fetch option.","Wrap your fetch to repair null bodies: read arrayBuffer() and return new Response(buf, ...) so .body is a ReadableStream.","Check response.status/body before streaming and surface a clear error for 204/304 instead of the raw guard.","In tests, make the mock return new Response(new ReadableStream({...}) , ...) rather than an empty Response."],"exampleFix":"// before\nmakeHttpStream({ url, fetch: mockFetch }); // mockFetch returns body-less Responses\n\n// after\nconst streamingFetch: typeof fetch = async (input, init) => {\n  const res = await fetch(input, init);\n  if (res.body == null) {\n    const buf = await res.arrayBuffer();\n    return new Response(buf, { status: res.status, headers: res.headers });\n  }\n  return res;\n};\nmakeHttpStream({ url, fetch: streamingFetch });","handlingStrategy":"fallback","validationCode":"const streamingFetch: typeof fetch = async (input, init) => {\n  const res = await fetch(input, init);\n  if (res.body == null) {\n    const buf = await res.arrayBuffer();\n    return new Response(buf, { status: res.status, headers: res.headers });\n  }\n  return res;\n};\n// pass as opts.fetch so response.body.getReader() always exists","typeGuard":"const hasStreamingBody = (res: Response): boolean =>\n  res.body != null && typeof res.body.getReader === \"function\";","tryCatchPattern":"try {\n  await sessionOperation();\n} catch (e) {\n  if (e instanceof Error && /reader is null|fetch error/.test(e.message)) {\n    // poisoned stream: swap in a streaming-capable fetch via opts.fetch and retry\n  } else throw e;\n}","preventionTips":["Provide a fetch whose Responses have web ReadableStream bodies (native fetch/undici).","Make test mocks return new Response(readableStream, ...), not empty Responses.","Watch for 204/304 or proxy responses that legitimately carry no body and handle them upstream.","Pair with retryFetch for transient 5xx/429 so streams are re-established cleanly."],"tags":["sync","fetch","streaming","http","response-body"],"backgroundTag":"null-response-body","analyzedSha":"6c7252267988c76e632af00a671e4b9788dfae13","analyzedAt":"2026-08-20T07:02:18.389Z","contentChangedAt":"2026-08-20T07:02:18.389Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}