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

  1. Use the platform's native fetch (Node >= 18, undici-based) or undici's fetch directly as opts.fetch.
  2. If you must wrap fetch, return the original Response untouched (or construct bodies from web streams: new Response(new ReadableStream(...))).
  3. Convert Node streams to web streams before constructing the Response (Readable.toWeb).
  4. 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

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


AI-assisted analysis of tursodatabase/turso@bad083fafb (2026-08-16). Data as JSON: /api/errors/a22598cbd9ba4660. Report an issue: GitHub.