# fetch failed: invalid onError method on Node 26 (undici fix)

Source: https://ipvolt.com/guides/node-26-invalid-onerror-method
Markdown: https://ipvolt.com/guides/node-26-invalid-onerror-method.md

[Home](https://ipvolt.com/index.md) / [Guides](https://ipvolt.com/guides.md) / [fetch failed: invalid onError method on Node 26 (undici fix)](https://ipvolt.com/guides/node-26-invalid-onerror-method.md)

Category: Troubleshooting
Published: 2026-11-07
Reading time: 9 minutes
Author: ipvolt

Node.js 26 fetch fails with invalid onError method (UND_ERR_INVALID_ARG) when given an undici 5 or 6 Agent or ProxyAgent. Fix: use undici's own fetch.

On Node.js 26, `fetch()` fails with `TypeError: fetch failed` and the cause `invalid onError method` (`UND_ERR_INVALID_ARG`) when it is given a dispatcher built by an older npm `undici`: a plain `Agent`, a keep-alive agent, an SSRF guard, a client library's bundled agent or, most often, a proxy agent. The request never leaves the process. Every version, error string and result below comes from a lab run on 7 November 2026 or from the linked documentation and issues.

## Quick fix

```sh
node -p "process.versions.undici"
npm ls undici
```

If the first command prints 8.x and the second lists `undici@5` or `undici@6`, that copy is the likely source. The fix that worked with every undici version on both Node lines in our tests is to import `fetch` from the package that builds the dispatcher: `import { fetch, ProxyAgent } from 'undici'` (Fix A below). If the dispatcher only exists to read `HTTPS_PROXY`, remove it and start Node with `--use-env-proxy`.

Tested on Node.js 26.13.0 (bundled undici 8.12.2) and 24.22.2 (bundled undici 7.30.1) with npm undici 5.29.0, 6.29.1, 7.30.2 and 8.12.4, on macOS 26.5.1 arm64, 7 November 2026. The scripts and all 166 recorded cases are in the [lab folder](/downloads/node-26-invalid-onerror-method/README.md).

## The error: UND_ERR_INVALID_ARG in err.cause

This is the complete output of the minimal reproduction shown further down, which passes an npm undici 6.29.1 `Agent` to the built-in `fetch` on Node.js 26.13.0. The lab path is shortened to `/lab`:

```text
node:internal/modules/run_main:111
    triggerUncaughtException(
    ^

[TypeError: fetch failed] {
  [cause]: InvalidArgumentError: invalid onError method
      at Agent.dispatch (/lab/u6/node_modules/undici/lib/dispatcher/dispatcher-base.js:198:15)
      at node:internal/deps/undici/undici:15144:57
      at new Promise (<anonymous>)
      at dispatchWithProtocolPreference (node:internal/deps/undici/undici:15144:18)
      at dispatch (node:internal/deps/undici/undici:15142:16)
      at httpNetworkFetch (node:internal/deps/undici/undici:15040:73)
      at httpNetworkOrCacheFetch (node:internal/deps/undici/undici:14912:39)
      at httpFetch (node:internal/deps/undici/undici:14729:43)
      at schemeFetch (node:internal/deps/undici/undici:14637:18)
      at mainFetch (node:internal/deps/undici/undici:14481:30) {
    code: 'UND_ERR_INVALID_ARG',
    Symbol(undici.error.UND_ERR): true,
    Symbol(undici.error.UND_ERR_INVALID_ARG): true
  }
}

Node.js v26.13.0
```

The first `at` line names the copy of undici that threw: here `/lab/u6/node_modules/undici`. With a `ProxyAgent` the trace gains one line, `at ProxyAgent.dispatch`, and the message is the same. The same script on Node.js 24.22.2 printed `200`. Code that catches the error and logs only `err.message` prints just `fetch failed`; the reason is in `err.cause`.

## Why Node 26 rejects older undici dispatchers

The built-in `fetch` is a copy of undici bundled into Node, and `process.versions.undici` tells you which one. The `dispatcher` option hands each request to the object you pass, so two undici versions have to agree on how they talk to each other. undici 8 renamed the handler callbacks a dispatcher calls, for example `onError` to `onResponseError` ([migration guide](https://undici.nodejs.org/best-practices/migrating-from-v7-to-v8)). Node 26's `fetch` passes a handler with only the new callbacks. An undici 5 or 6 dispatcher checks for the old `onError`, finds none and throws before it opens a connection. undici 7 dispatchers accepted the new callbacks in our tests.

The minimal reproduction:

```js
import { Agent } from 'undici';

const res = await fetch('https://example.com/', { dispatcher: new Agent() });
console.log(res.status);
```

The same `Agent`, `ProxyAgent` and `EnvHttpProxyAgent` cases, passed per request to the built-in `fetch`:

| npm undici | Node.js 26.13.0 (undici 8.12.2) | Node.js 24.22.2 (undici 7.30.1) |
| --- | --- | --- |
| 5.29.0 | `invalid onError method` | works |
| 6.29.1 | `invalid onError method` | works |
| 7.30.2 | works | works |
| 8.12.4 | works | `invalid onRequestStart method` |

undici 5.29.0 has no `EnvHttpProxyAgent`; every other cell held for all three agent types. Older releases behaved the same on Node 26: `Agent` and `ProxyAgent` from 5.0.0, 5.28.4, 6.0.0 and 6.21.0 failed with `invalid onError method`, and from the eight 7.x releases we tried, 7.0.0 to 7.28.0, they worked. The last row is the same mismatch in reverse: Node 24's `fetch` passes a handler without `onRequestStart`, and an undici 8 dispatcher rejects it.

## Find what's bundling an old undici

Run both commands with the same `node` and in the same project as the failing code:

```sh
node -p "process.versions.undici"
npm ls undici
```

The first printed `8.12.2` on Node 26.13.0 and `7.30.1` on Node 24.22.2. In a test project with its own undici 8 and `cheerio@1.0.0`, the second printed:

```text
my-app@1.0.0 /lab/npm-ls-demo/my-app
├─┬ cheerio@1.0.0
│ └── undici@6.29.1
└── undici@8.12.4
```

The top-level `undici@8.12.4` is the project's own copy. `cheerio@1.0.0` brings its own `undici@6.29.1`. A 5.x or 6.x entry is a candidate, not proof: it fails only if a dispatcher it builds is passed to the built-in `fetch`. The stack trace settles it, because its first `at` line shows the path of the copy that threw. A tool that compiles undici into a single file does not appear in `npm ls` at all; the Corepack trace in [corepack#834](https://github.com/nodejs/corepack/issues/834) points into `corepack.cjs`.

## Fixes A and B: pair fetch with its own undici

### Fix A: use undici's own fetch

Import `fetch` from the same package as the dispatcher, so both halves always come from one version:

```js
import { fetch, ProxyAgent } from 'undici';

const dispatcher = new ProxyAgent(process.env.HTTPS_PROXY);
const res = await fetch('https://example.com/', { dispatcher });
console.log(res.status);
```

In the lab this worked with undici 5.29.0, 6.29.1, 7.30.2 and 8.12.4 on both Node 26.13.0 and 24.22.2, with `Agent` and `ProxyAgent`, and with `EnvHttpProxyAgent` where the version has one; every proxied request reached the proxy. Use it in libraries, CLIs and anything that runs on more than one Node line. Keep `FormData` and other body classes from the same package as `fetch` ([undici docs](https://undici.nodejs.org/best-practices/undici-vs-builtin-fetch)). The Serverless and career-ops fixes and the open Vercel CLI pull request linked below take this route.

### Fix B: match the major to process.versions.undici

```sh
npm install undici@8
```

With undici 8.12.4, the built-in `fetch` on Node 26.13.0 accepted `Agent`, `ProxyAgent` and `EnvHttpProxyAgent`. On Node 24.22.2 the same dispatchers failed with `invalid onRequestStart method`, so this fix ties your code to one Node line. undici 8 needs Node.js 22.19.0 or later ([migration guide](https://undici.nodejs.org/best-practices/migrating-from-v7-to-v8)). undici 7.30.2 worked on both lines in the lab, and Redocly CLI and the Qdrant client moved to undici 7; that is a snapshot of two Node releases, not a promise about the next one.

## Behind a proxy: ProxyAgent, EnvHttpProxyAgent, setGlobalDispatcher

`ProxyAgent` and `EnvHttpProxyAgent` from undici 5 or 6 fail exactly like a plain `Agent`. Adding `NODE_USE_ENV_PROXY=1` does not help while your code still passes the old dispatcher: an undici 6.29.1 `ProxyAgent` failed with `invalid onError method` on Node 26.13.0 with the variable set.

`setGlobalDispatcher()` fails more quietly. On Node 26.13.0, a global `ProxyAgent` from undici 5.29.0, 6.29.1 or the 7.x releases we tried before 7.27.0 (7.0.0 to 7.26.0), and a global `EnvHttpProxyAgent` from 6.29.1, did not throw. The built-in `fetch` ignored them and connected directly. Against `https://example.com`, an undici 6.29.1 global `ProxyAgent` got a `200` while the proxy logged nothing, so the request left from our own IP; on Node 24.22.2 the same script went through the proxy. undici 7.27.0, 7.28.0, 7.30.2 and 8.12.4 worked globally. None of the older versions writes `Symbol.for('undici.globalDispatcher.2')`, where undici 8 [keeps its global dispatcher](https://undici.nodejs.org/best-practices/migrating-from-v7-to-v8); 7.27.0 and later do.

### Fix C: let Node read the proxy variables

If the dispatcher only exists to send traffic through `HTTPS_PROXY`, remove it and let Node do it:

```sh
export HTTPS_PROXY=http://USER:PASS@proxy.example:8080
export NO_PROXY=localhost,127.0.0.1
node --use-env-proxy app.mjs
```

`NODE_USE_ENV_PROXY=1`, the `--use-env-proxy` flag and `NODE_OPTIONS=--use-env-proxy` all worked on Node 26.13.0 and 24.22.2, for `fetch` and for `https.get`. The opt-in arrived for `fetch` first, in [this Node commit](https://github.com/nodejs/node/commit/ada34bd0ea); the [Node docs](https://nodejs.org/api/cli.html#--use-env-proxy) list `NODE_USE_ENV_PROXY` from 24.0.0 and `--use-env-proxy` from 24.5.0, and both from 22.21.0. What the lab showed:

- **Read at startup only.** Setting `process.env.NODE_USE_ENV_PROXY = '1'` inside the script did nothing. To switch on at runtime, call `http.setGlobalProxyFromEnv()` ([added in 24.14.0 and 25.4.0](https://nodejs.org/api/http.html#httpsetglobalproxyfromenvproxyenv)); it proxied both `fetch` and `https.get`.
- **HTTP_PROXY alone.** With only `HTTP_PROXY` set, `fetch` used it for an `https://` URL, but `https.get` connected directly. Set `HTTPS_PROXY`.
- **NO_PROXY.** `target.test`, `.test`, `*.test`, `*`, `target.test:8443` and lowercase `no_proxy` all bypassed the proxy; `target.test:9999` did not, because the port differs. With `NO_PROXY` unset, requests to `localhost` and `127.0.0.1` went through the proxy; `NO_PROXY=localhost,127.0.0.1` sent them direct.

Node 26's `fetch` negotiated HTTP/2 with the lab's target, inside the CONNECT tunnel too, where Node 24 used HTTP/1.1. The proxy saw the same `CONNECT` line either way, and no request failed because of it.

## Vercel CLI, Corepack, Redocly and other tools you don't control

Checked on 7 November 2026:

- **Vercel CLI.** [vercel#17629](https://github.com/vercel/vercel/issues/17629) is open: with any proxy variable set, versions 59.18.0 and 59.23.1 fail on Node 26.8.2 with `invalid onError method` from a bundled undici 5.29.0 and work on Node 24.21.0. The fix, [vercel#17634](https://github.com/vercel/vercel/pull/17634), is not merged. Until a release has it, run the CLI on Node 24.
- **Corepack.** [corepack#834](https://github.com/nodejs/corepack/issues/834) reports a bundled undici 6 `ProxyAgent` failing on Node 26.0.0 and 26.1.0. [Corepack 0.35.0](https://github.com/nodejs/corepack/releases/tag/v0.35.0) dropped undici and uses Node's proxy support, so set `NODE_USE_ENV_PROXY=1`; the reporter confirmed that combination works. The Node 26.13.0 tarball we installed has no `corepack` binary (24.22.2 has one), so check which version you installed with `corepack --version`.
- **Redocly CLI.** [redocly-cli#3232](https://github.com/Redocly/redocly-cli/issues/3232) covers `login`, `push` and the other Reunite commands. [2.62.2](https://github.com/Redocly/redocly-cli/releases/tag/%40redocly/cli%402.62.2) fixed them on Node 26 and moved to undici 7.30.0.
- **Serverless Framework.** [4.44.0](https://github.com/serverless/serverless/releases/tag/sf-core%404.44.0) fixed the npm package's proxied download on Node 26 ([#13918](https://github.com/serverless/serverless/pull/13918)), and [4.45.0](https://github.com/serverless/serverless/releases/tag/sf-core%404.45.0) fixed its API requests through a proxy on Node 26.
- **Client libraries, no proxy involved.** [SocratiCode#154](https://github.com/giancarloerra/SocratiCode/issues/154): `@qdrant/js-client-rest` before 1.19 hands an undici 6 `Agent` to the built-in `fetch`, and 1.19.0 moved to undici 7. [almyty#702](https://github.com/almyty-inc/almyty/pull/702): an SSRF-pinning `Agent` from undici 6.27.0 failed on Node 26.9.0 and its custom DNS lookup never ran; the fix raised undici to ^8.11.0. [career-ops#4808](https://github.com/career-ops-hq/career-ops/issues/4808): proxy egress with an undici 6 `EnvHttpProxyAgent` failed on Node 26; v1.36.0 pairs it with undici's own `fetch`, the same approach as the earlier [#4787](https://github.com/career-ops-hq/career-ops/pull/4787).

For any other tool: upgrade it, run it on Node 24 meanwhile, and report the error with its `cause` and the first `at` line.

## Check it really goes through the proxy

A response with status 200 does not prove the proxy carried it: an ignored global dispatcher or a `NO_PROXY` match sends the request direct, and a reachable site still answers 200. Look at the proxy's own log first; in the lab every proxied request left one `CONNECT` line with the host and port, and the bypassed ones left none. Without access to the proxy log, ask an IP-echo service:

```js
const res = await fetch('https://api.ipify.org?format=json');
console.log(await res.json());
```

Run it once through your proxy setup, for example with `HTTPS_PROXY` set and `node --use-env-proxy`, and once without it. If both print the same IP, the request went around the proxy.

## FAQ

### Does undici 7 cause "invalid onError method" on Node.js 26?

Not in our tests. `Agent` and `ProxyAgent` from every 7.x release we tried, 7.0.0 to 7.30.2, worked when passed per request, and so did `EnvHttpProxyAgent` from 7.30.2. `setGlobalDispatcher()` from 7.x before 7.27.0 was ignored without an error.

### Why does it work on Node 24 and fail on Node 26?

Node 24.22.2 bundles undici 7.30.1, which accepted undici 5 and 6 dispatchers. Node 26.13.0 bundles undici 8.12.2, which sends only the new callbacks.

### Does NODE_USE_ENV_PROXY=1 fix it?

Only after you remove the old dispatcher. With an undici 6 `ProxyAgent` still passed to `fetch`, Node 26 failed the same way with the variable set.

### I see "invalid onRequestStart method" instead. Is it the same problem?

Yes, in reverse: an undici 8 dispatcher passed to the `fetch` of an older Node line; we saw it on Node 24.22.2. Fix A works there too.

## Related guides

- [Fix Node.js TypeError: fetch failed behind a proxy](/guides/fix-node-fetch-failed-proxy) reads every cause in the chain, not only this one.
- [Use a proxy with Node.js fetch](/guides/nodejs-fetch-proxy) sets up a `ProxyAgent` with authentication and a deadline.
- [Node.js fetch ignores HTTPS_PROXY](/guides/node-fetch-ignores-https-proxy) covers the timeout you get without the opt-in.
- [Proxy environment variables](/guides/proxy-environment-variables) compares how curl, Requests and Node read them.
- [npm proxy settings](/guides/npm-proxy-settings) gets `npm install undici` through a proxy.

**Test the fix through a real carrier IP.** An ipvolt [mobile proxy](/pricing) is a dedicated device on a carrier network, from $9 a day. Put its URL in `HTTPS_PROXY` and run the IP check above with `--use-env-proxy`.

## Sources & further reading

- [undici: Migrating from undici 7 to 8 (handler callbacks, global dispatcher)](https://undici.nodejs.org/best-practices/migrating-from-v7-to-v8)
- [undici: Undici module vs. Node.js built-in fetch](https://undici.nodejs.org/best-practices/undici-vs-builtin-fetch)
- [Node.js CLI: --use-env-proxy](https://nodejs.org/api/cli.html#--use-env-proxy)
- [Node.js CLI: NODE_USE_ENV_PROXY=1](https://nodejs.org/api/cli.html#node_use_env_proxy1)
- [Node.js HTTP: Built-in proxy support and NO_PROXY format](https://nodejs.org/api/http.html#built-in-proxy-support)
- [Node.js HTTP: http.setGlobalProxyFromEnv()](https://nodejs.org/api/http.html#httpsetglobalproxyfromenvproxyenv)
- [Node.js commit ada34bd: proxy support for fetch under NODE_USE_ENV_PROXY](https://github.com/nodejs/node/commit/ada34bd0ea)
- [vercel/vercel#17629: CLI fails on Node.js 26 with a proxy variable set](https://github.com/vercel/vercel/issues/17629)
- [vercel/vercel#17634: route the CLI through its bundled undici fetch](https://github.com/vercel/vercel/pull/17634)
- [nodejs/corepack#834: bundled undici 6 ProxyAgent on Node.js 26](https://github.com/nodejs/corepack/issues/834)
- [Corepack 0.35.0 release notes](https://github.com/nodejs/corepack/releases/tag/v0.35.0)
- [Redocly/redocly-cli#3232: Reunite commands fail on Node.js 26](https://github.com/Redocly/redocly-cli/issues/3232)
- [Redocly CLI 2.62.2 release notes](https://github.com/Redocly/redocly-cli/releases/tag/%40redocly/cli%402.62.2)
- [serverless/serverless#13918: proxied binary downloads on Node.js 26](https://github.com/serverless/serverless/pull/13918)
- [Serverless Framework 4.44.0 release notes](https://github.com/serverless/serverless/releases/tag/sf-core%404.44.0)
- [Serverless Framework 4.45.0 release notes](https://github.com/serverless/serverless/releases/tag/sf-core%404.45.0)
- [giancarloerra/SocratiCode#154: Qdrant client before 1.19 on Node.js 26](https://github.com/giancarloerra/SocratiCode/issues/154)
- [almyty-inc/almyty#702: SSRF-pinning agent and undici 8](https://github.com/almyty-inc/almyty/pull/702)
- [career-ops-hq/career-ops#4808: proxy egress fails on Node.js 26](https://github.com/career-ops-hq/career-ops/issues/4808)
- [career-ops-hq/career-ops#4787: drive the proxy agent with undici's own fetch](https://github.com/career-ops-hq/career-ops/pull/4787)

## Related guides

- [Fix Node.js TypeError: fetch failed behind a proxy](https://ipvolt.com/guides/fix-node-fetch-failed-proxy.md)
- [Use a proxy with Node.js fetch](https://ipvolt.com/guides/nodejs-fetch-proxy.md)
- [Node.js fetch Ignores HTTPS_PROXY: Fix UND_ERR_CONNECT_TIMEOUT in One Line](https://ipvolt.com/guides/node-fetch-ignores-https-proxy.md)
- [Proxy environment variables: HTTP_PROXY and NO_PROXY](https://ipvolt.com/guides/proxy-environment-variables.md)
- [npm proxy settings: npmrc, precedence and install failures](https://ipvolt.com/guides/npm-proxy-settings.md)

## About ipvolt

Examples use generic proxy settings, with links to the original technical documentation. Product-specific behavior must be checked with your provider. ipvolt sells mobile proxies and plans residential proxies from $2.40/GB.

## Plug in your first proxy.

Mobile from $9 a day. Residential from $2.40 per GB, opening soon.

[Buy mobile](https://ipvolt.com/dashboard/mobile)

## Residential launch list

Leave your email and we’ll tell you when residential proxies open.

Consent: One email when access opens. Nothing else.

[Join the launch list](https://ipvolt.com/guides/node-26-invalid-onerror-method#waitlist-closing). Use the email form on this page to join the launch list.

[Privacy](https://ipvolt.com/privacy)

