Skip to content

Fix Node 26+ compatibility (UND_ERR_INVALID_ARG) - #146

Open
IvanPleshkov wants to merge 2 commits into
masterfrom
fix-unidicy-134
Open

Fix Node 26+ compatibility (UND_ERR_INVALID_ARG)#146
IvanPleshkov wants to merge 2 commits into
masterfrom
fix-unidicy-134

Conversation

@IvanPleshkov

Copy link
Copy Markdown
Contributor

Fix: Node 26+ compatibility — UND_ERR_INVALID_ARG: invalid onError method

Fixes #134

On Node 26+ every request throws TypeError: fetch failed with cause UND_ERR_INVALID_ARG: invalid onError method.

This is the "two undici in one process" problem. The REST client pinned undici@^6 and built its own undici. Agent, then passed it as init. dispatcher to Node's global fetch. On Node ≤22, the bundled undici accepted the v6 Agent; Node 26 bundles undici 8.x, whose dispatcher hook contract is stricter and rejects the v6 Agent during dispatch init.

Fix

The request no longer flows through the global fetch with a foreign dispatcher. A terminal middleware now performs the request itself with a resolved fetch, picked in this order:

A - a caller-supplied fetch (new optional fetch option on QdrantClient);
B (default on Node) - undici's own fetch paired with an undici Agent from the same package, so the dispatcher contract always matches regardless of the undici version Node ships;
C - otherwise the platform's global fetch.

Notes:

The undici-backed transport lives in its own module (src/node-fetch.ts) and is referenced only behind a process guard.
The terminal middleware re-implements the dependency's response parsing, including the bigint reviver, so large-integer handling is preserved.
Public API is unchanged except for the new optional, backward-compatible fetch option.

How to reproduce / verify

Reproduced and verified with real client builds on node:26-alpine (Node v26.3.1) against a local Qdrant-shaped HTTP server.

repro.mjs file:

import http from 'node:http';
import {QdrantClient} from './dist/esm/index.js';

const server = http.createServer((req, res) => {
    res.setHeader('content-type', 'application/json');
    res.end(req.url.endsWith('/exists')
        ? JSON.stringify({result: {exists: true}, status: 'ok', time: 0})
        : JSON.stringify({result: {collections: []}, status: 'ok', time: 0}));
});
await new Promise((r) => server.listen(0, r));
const {port} = server.address();

const client = new QdrantClient({url: `http://127.0.0.1:${port}`, checkCompatibility: false});
try {
    console.log(await client.collectionExists('my-collection'));
    console.log(await client.getCollections());
    console.log('WORKS ✓');
} catch (e) {
    console.log('FAILED:', e.code ?? e.name, '-', e.message, '| cause:', e.cause?.code, e.cause?.message);
    process.exitCode = 1;
} finally {
    server.close();
}

Run on Node 26:

pnpm --filter @qdrant/js-client-rest build
docker run --rm -v "$PWD":"$PWD" -w "$PWD/packages/js-client-rest" node:26-alpine node repro.mjs

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Node 26+: UND_ERR_INVALID_ARG: invalid onError method on first request (Agent/fetch undici version mismatch)

1 participant