tursodatabase/turso · error · Error
reader is null
Error message
reader is null
What it means
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.
Source
Thrown at bindings/javascript/sync/packages/common/run.ts:57
url = normalizeUrl(url);
try {
let headers = typeof opts.headers === "function" ? await opts.headers() : opts.headers;
if (requestType.headers != null && requestType.headers.length > 0) {
headers = { ...headers };
for (let header of requestType.headers) {
headers[header[0]] = header[1];
}
}
const fetchImpl = opts.fetch ?? fetch;
const response = await fetchImpl(`${url}${requestType.path}`, {
method: requestType.method,
headers: headers,
body: requestType.body != null ? new Uint8Array(requestType.body) : null,
});
completion.status(response.status);
const reader = response.body?.getReader();
if (reader == null) {
throw new Error("reader is null");
}
while (true) {
const { done, value } = await reader.read();
if (done) {
completion.done();
break;
}
completion.pushBuffer(value);
}
} catch (error) {
completion.poison(`fetch error: ${error}`);
}
} else if (requestType.type == 'FullRead') {
try {
const metadata = await io.read(requestType.path);
if (metadata != null) {
completion.pushBuffer(metadata);
}View on GitHub (pinned to bad083fafb)
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.
Example fix
// before
import nodeFetch from 'node-fetch';
const engine = createEngine({ url, fetch: nodeFetch }); // node-fetch v2: body has no getReader()
// after
// Node >= 18: omit `fetch` so global fetch is used, or pass undici explicitly
import { fetch as undiciFetch } from 'undici';
const engine = createEngine({ url, fetch: undiciFetch }); Defensive patterns
Strategy: type-guard
Validate before calling
// validate your custom fetch before wiring it into the runner
const safeFetch: typeof fetch = async (input, init) => {
const res = await fetch(input, init);
if (!res.body) throw new Error('fetch impl returned a Response without a streaming body');
return res;
}; Type guard
const hasStreamingBody = (r: Response): r is Response & { body: ReadableStream<Uint8Array> } =>
r.body != null && typeof (r.body as any).getReader === 'function'; Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- No active remote transaction
- retryFetch: attempts must be a finite integer >= 1, got ${at
- remoteWritesExperimental requires a non-null URL
- sync is disabled as database was opened without sync support
- HTTP request failed: ${e instanceof Error ? e.message : Stri
AI-assisted analysis of tursodatabase/turso@bad083fafb (2026-08-16).
Data as JSON: /api/errors/a22598cbd9ba4660.
Report an issue: GitHub.