Skip to content

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

Description

@shaitourchin

Summary

On Node 26.x (and presumably any future major that ships a stricter undici), the first request through @qdrant/js-client-rest fails with:

UND_ERR_INVALID_ARG: invalid onError method

The cause appears to be a version mismatch between two undicis in the same process:

  • @qdrant/js-client-rest pins undici: ^6.23.0 and constructs an undici.Agent (in packages/js-client-rest/src/dispatcher.ts).
  • It passes that Agent as the dispatcher option to Node's built-in fetch(), which on Node 26 uses a newer undici internally.
  • The two undicis disagree on the dispatcher hook contract — the newer one validates onError strictly and rejects the v6 Agent's shape on first dispatch.

The client itself has no onError in source (I grepped dist/); the error originates inside undici's internal validation pipeline when fetch tries to use the externally-constructed Agent.

Environment

@qdrant/js-client-rest 1.18.0 (latest as of filing); declared ^1.17.0 by downstream
Node (broken) 26.0.0
Node (works) 22.x
OS macOS 14 (also reported on Linux x86_64 per downstream notes)
Qdrant server v1.17.0

Reproduction (constructed; not directly executed)

I encountered this through socraticode — an MCP server that wraps the qdrant client — not by running the minimal script below. The script is reconstructed from inspecting dispatcher.ts and should exercise the same failing code path. If a maintainer can confirm or refute the minimal repro, that'll tell us whether the failure is purely in the dispatcher path or whether it also requires socraticode-specific usage.

# Start a Qdrant instance (any 1.x image)
docker run --rm -d -p 6333:6333 --name qdrant-repro qdrant/qdrant:v1.17.0

# Minimal client repro
mkdir qdrant-undici-repro && cd qdrant-undici-repro
npm init -y >/dev/null
npm pkg set type=module >/dev/null
npm install @qdrant/js-client-rest@1.18.0 >/dev/null

cat > repro.mjs <<'EOF'
import { QdrantClient } from '@qdrant/js-client-rest';
const client = new QdrantClient({ url: 'http://localhost:6333' });
console.log(await client.getCollections());
EOF

node --version
node repro.mjs

Expected (observed on Node 22.x)

v22.x.x
{ collections: [] }

Actual (Node 26.0.0)

v26.0.0
node:internal/deps/undici/undici:...
    throw new InvalidArgumentError('invalid onError method')
    ^
TypeError [UND_ERR_INVALID_ARG]: invalid onError method
    at ...

Root cause (best guess)

packages/js-client-rest/src/dispatcher.ts does:

import { Agent } from 'undici';
export const createDispatcher = (connections = 25) =>
  new Agent({ /* ... */ });

When this Agent (from the npm-installed undici@^6) is passed to Node's built-in fetch(), fetch routes the dispatch through Node's bundled undici. The bundled undici has tightened its dispatcher-hook validation between Node 22's vendored version and Node 26's — it now requires the request handler's onError to be a function with a specific signature, and the v6 Agent doesn't satisfy it.

This is structurally a "two undicis in one process" problem. As long as the client constructs its dispatcher with one undici version and fetch handles it with another, contract drift between them will keep surfacing as cryptic errors.

Suggested fixes (already in flight)

Two existing open PRs in this repo would likely both fix this:

  • undici major upgrade #123 — undici major upgrade. If the client moves to undici 7 (or whatever Node 26 ships), both sides agree on the contract.
  • Inject fetch into REST transport #128 — Inject fetch into REST transport. If callers can supply their own fetch (e.g. from the same undici package the client uses), there's no two-undici problem at all.

I'd defer to maintainer judgment on which to prioritize, but #128 in particular sidesteps the entire class of bug — every future undici contract drift becomes a non-issue because the user controls both sides.

If neither lands soon, a minimal interim mitigation might be to import undici.fetch and undici.Agent from the same module (import { Agent, fetch } from 'undici') and use both, instead of mixing the npm Agent with Node's global fetch. That keeps the two sides on the same undici version regardless of the Node version.

Downstream impact

This bug affects any tool built on @qdrant/js-client-rest that runs under Node 26+. In our case it's socraticode (an MCP server that wraps a Qdrant-backed codebase index). I'm filing a separate PR there that refuses Node 26+ with a clear error message until this is resolved upstream — but it'd be much better to fix the root cause.

Happy to test a candidate fix on Node 26.x against a real workload.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions