{"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 ? new Uint8Array(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/bad083fafbefdeae9a42ec19bdaaad8918dcf411/bindings/javascript/sync/packages/common/run.ts#L39-L75","documentation":"The sync runner's HTTP loop consumes the response body as a stream: it calls response.body?.getReader() and throws 'reader is null' when the body is absent. The error is then wrapped by the completion as 'fetch error: reader is null'. It means the configured fetch implementation returned a Response without a WHATWG ReadableStream body, which the sync protocol requires to frame server frames.","triggerScenarios":"Passing opts.fetch = node-fetch v2 (Node stream bodies, no getReader); an axios- or got-based adapter returning a buffered Response whose body is null; constructing new Response(buffer) without a stream; environments whose global fetch strips bodies; responses with null body (204/304) that the sync server should not send.","commonSituations":"Instrumenting fetch for logging/auth with a wrapper that clones incorrectly; older Node runtimes (<18) with a polyfill; Electron main process with a non-standard fetch; proxies in front of the sync endpoint that drop streaming.","solutions":["Use the platform's native fetch (Node >= 18, undici-based) or undici's fetch directly as opts.fetch.","If you must wrap fetch, return the original Response untouched (or construct bodies from web streams: new Response(new ReadableStream(...))).","Convert Node streams to web streams before constructing the Response (Readable.toWeb).","Verify no proxy is downgrading responses to buffered/no-body."],"exampleFix":"// before\nimport nodeFetch from 'node-fetch';\nconst engine = createEngine({ url, fetch: nodeFetch }); // node-fetch v2: body has no getReader()\n\n// after\n// Node >= 18: omit `fetch` so global fetch is used, or pass undici explicitly\nimport { fetch as undiciFetch } from 'undici';\nconst engine = createEngine({ url, fetch: undiciFetch });","handlingStrategy":"type-guard","validationCode":"// validate your custom fetch before wiring it into the runner\nconst safeFetch: typeof fetch = async (input, init) => {\n  const res = await fetch(input, init);\n  if (!res.body) throw new Error('fetch impl returned a Response without a streaming body');\n  return res;\n};","typeGuard":"const hasStreamingBody = (r: Response): r is Response & { body: ReadableStream<Uint8Array> } =>\n  r.body != null && typeof (r.body as any).getReader === 'function';","tryCatchPattern":null,"preventionTips":["Run on Node >= 18 and use the global fetch or undici as opts.fetch.","Never pass node-fetch v2 to the sync runner.","When wrapping fetch for auth/logging, return the original Response unmodified.","Convert Node streams with Readable.toWeb before building a Response."],"tags":["sync","fetch","streaming","custom-fetch","javascript"],"backgroundTag":"fetch-response-without-body","analyzedSha":"bad083fafbefdeae9a42ec19bdaaad8918dcf411","analyzedAt":"2026-08-16T23:12:11.798Z","schemaVersion":2},"datasetVersion":"2026-08-17T04:17:16.089Z"}