# Node 26 报错 invalid onError method

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

[ipvolt 首页](https://ipvolt.com/zh.md) / [指南](https://ipvolt.com/zh/guides.md) / Node 26 报错 invalid onError method

故障排查
发布于: 2026-11-07
阅读约 9 分钟
作者： ipvolt

Node.js 26 的 fetch 收到旧版 undici 创建的 dispatcher 时会报 UND_ERR_INVALID_ARG: invalid onError method。本文说明如何找到旧副本并修复。

在 Node.js 26 上，如果传给 `fetch()` 的 dispatcher 是由旧版 npm `undici` 创建的，它就会以 `TypeError: fetch failed` 失败，原因是 `invalid onError method`（`UND_ERR_INVALID_ARG`）。这个 dispatcher 可能是普通的 `Agent`、keep-alive agent、SSRF 防护、客户端库自带的 agent，最常见的则是代理 agent。请求根本没有离开进程。下文的每个版本号、错误字符串和结果，都来自 2026 年 11 月 7 日的实验运行，或来自所链接的文档和 issue。

## 快速修复

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

如果第一条命令输出 8.x，而第二条列出了 `undici@5` 或 `undici@6`，那么这份副本很可能就是来源。在我们的测试中，对所有 undici 版本、在两条 Node 版本线上都有效的修复是：从创建 dispatcher 的同一个包里导入 `fetch`，即 `import { fetch, ProxyAgent } from 'undici'`（见下文修复 A）。如果这个 dispatcher 只是为了读取 `HTTPS_PROXY`，就删掉它，改用 `--use-env-proxy` 启动 Node。

测试环境：Node.js 26.13.0（内置 undici 8.12.2）和 24.22.2（内置 undici 7.30.1），npm undici 5.29.0、6.29.1、7.30.2 和 8.12.4，macOS 26.5.1 arm64，2026 年 11 月 7 日。脚本和全部 166 个记录的用例都在[实验文件夹](/downloads/node-26-invalid-onerror-method/README.md)中。

## 错误信息：err.cause 中的 UND_ERR_INVALID_ARG

下面是后文最小复现脚本的完整输出：它把 npm undici 6.29.1 的 `Agent` 传给 Node.js 26.13.0 的内置 `fetch`。实验路径已缩写为 `/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
```

第一行 `at` 指出了抛出错误的 undici 副本：这里是 `/lab/u6/node_modules/undici`。换成 `ProxyAgent` 时，堆栈会多出一行 `at ProxyAgent.dispatch`，错误信息不变。同一个脚本在 Node.js 24.22.2 上输出 `200`。如果代码捕获错误后只记录 `err.message`，你只会看到 `fetch failed`；真正的原因在 `err.cause` 里。

## 为什么 Node 26 会拒绝旧版 undici 的 dispatcher

内置 `fetch` 是打包进 Node 的一份 undici，`process.versions.undici` 会告诉你是哪个版本。`dispatcher` 选项会把每个请求交给你传入的对象，所以两个 undici 版本必须在彼此通信的方式上达成一致。undici 8 重命名了 dispatcher 调用的处理器回调，例如 `onError` 改成了 `onResponseError`（[迁移指南](https://undici.nodejs.org/best-practices/migrating-from-v7-to-v8)）。Node 26 的 `fetch` 传入的处理器只带新回调。undici 5 或 6 的 dispatcher 会检查旧的 `onError`，找不到就在建立连接之前抛出错误。在我们的测试中，undici 7 的 dispatcher 接受了新回调。

最小复现：

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

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

把 `Agent`、`ProxyAgent` 和 `EnvHttpProxyAgent` 按请求传给内置 `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` | 正常 |
| 6.29.1 | `invalid onError method` | 正常 |
| 7.30.2 | 正常 | 正常 |
| 8.12.4 | 正常 | `invalid onRequestStart method` |

undici 5.29.0 没有 `EnvHttpProxyAgent`；其余每个单元格对三种 agent 都成立。较旧的版本在 Node 26 上表现相同：5.0.0、5.28.4、6.0.0 和 6.21.0 的 `Agent` 与 `ProxyAgent` 都以 `invalid onError method` 失败，而我们试过的八个 7.x 版本（7.0.0 到 7.28.0）都正常。最后一行是方向相反的同一种错配：Node 24 的 `fetch` 传入的处理器没有 `onRequestStart`，undici 8 的 dispatcher 会拒绝它。

## 找出是谁带来了旧版 undici

用同一个 `node`、在出错代码所在的同一个项目里运行这两条命令：

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

第一条在 Node 26.13.0 上输出 `8.12.2`，在 Node 24.22.2 上输出 `7.30.1`。在一个自带 undici 8 并安装了 `cheerio@1.0.0` 的测试项目里，第二条输出：

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

顶层的 `undici@8.12.4` 是项目自己的副本，`cheerio@1.0.0` 则带来了它自己的 `undici@6.29.1`。5.x 或 6.x 条目只是嫌疑对象，不是证据：只有当它创建的 dispatcher 被传给内置 `fetch` 时才会出错。堆栈可以给出定论，因为它的第一行 `at` 显示了抛出错误的那份副本的路径。把 undici 编译进单个文件的工具根本不会出现在 `npm ls` 中；[corepack#834](https://github.com/nodejs/corepack/issues/834) 里的 Corepack 堆栈指向的是 `corepack.cjs` 内部。

## 修复 A 和 B：让 fetch 与 dispatcher 来自同一个 undici

### 修复 A：使用 undici 自己的 fetch

从 dispatcher 所在的同一个包导入 `fetch`，让两者始终来自同一个版本：

```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);
```

在实验中，它在 Node 26.13.0 和 24.22.2 上对 undici 5.29.0、6.29.1、7.30.2 和 8.12.4 都有效，适用于 `Agent` 和 `ProxyAgent`，以及有 `EnvHttpProxyAgent` 的版本中的 `EnvHttpProxyAgent`；每个走代理的请求都到达了代理。库、CLI 以及需要在多条 Node 版本线上运行的代码都应该这样做。`FormData` 等请求体类也要和 `fetch` 来自同一个包（[undici 文档](https://undici.nodejs.org/best-practices/undici-vs-builtin-fetch)）。下文链接的 Serverless 和 career-ops 修复，以及 Vercel CLI 尚未合并的 pull request，走的都是这条路。

### 修复 B：主版本与 process.versions.undici 一致

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

使用 undici 8.12.4 时，Node 26.13.0 的内置 `fetch` 接受了 `Agent`、`ProxyAgent` 和 `EnvHttpProxyAgent`。在 Node 24.22.2 上，同样的 dispatcher 以 `invalid onRequestStart method` 失败，所以这种修复会把你的代码绑定在一条 Node 版本线上。undici 8 需要 Node.js 22.19.0 或更高版本（[迁移指南](https://undici.nodejs.org/best-practices/migrating-from-v7-to-v8)）。实验中 undici 7.30.2 在两条版本线上都正常，Redocly CLI 和 Qdrant 客户端也改用了 undici 7；但这只是两个 Node 版本的快照，并不保证下一个版本也如此。

## 使用代理时：ProxyAgent、EnvHttpProxyAgent 与 setGlobalDispatcher

undici 5 或 6 的 `ProxyAgent` 和 `EnvHttpProxyAgent` 与普通 `Agent` 的失败方式完全相同。只要代码仍在传入旧的 dispatcher，加上 `NODE_USE_ENV_PROXY=1` 也没用：设置了该变量后，undici 6.29.1 的 `ProxyAgent` 在 Node 26.13.0 上仍以 `invalid onError method` 失败。

`setGlobalDispatcher()` 的失败更隐蔽。在 Node 26.13.0 上，来自 undici 5.29.0、6.29.1 或我们试过的 7.27.0 之前的 7.x 版本（7.0.0 到 7.26.0）的全局 `ProxyAgent`，以及来自 6.29.1 的全局 `EnvHttpProxyAgent`，都没有抛出错误。内置 `fetch` 直接忽略了它们并直连。请求 `https://example.com` 时，undici 6.29.1 的全局 `ProxyAgent` 拿到了 `200`，而代理日志里什么都没有，也就是说请求是从我们自己的 IP 发出的；同一个脚本在 Node 24.22.2 上则走了代理。undici 7.27.0、7.28.0、7.30.2 和 8.12.4 的全局设置都有效。旧版本都不会写入 `Symbol.for('undici.globalDispatcher.2')`，而 undici 8 [把全局 dispatcher 保存在那里](https://undici.nodejs.org/best-practices/migrating-from-v7-to-v8)；7.27.0 及以后的版本会写入。

### 修复 C：让 Node 自己读取代理变量

如果 dispatcher 只是为了让流量走 `HTTPS_PROXY`，就删掉它，交给 Node 处理：

```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`、`--use-env-proxy` 参数和 `NODE_OPTIONS=--use-env-proxy` 在 Node 26.13.0 和 24.22.2 上都有效，`fetch` 和 `https.get` 都适用。这项开关最先是为 `fetch` 加入的，见[这个 Node 提交](https://github.com/nodejs/node/commit/ada34bd0ea)；[Node 文档](https://nodejs.org/api/cli.html#--use-env-proxy)记载 `NODE_USE_ENV_PROXY` 从 24.0.0 起、`--use-env-proxy` 从 24.5.0 起可用，两者在 22.21.0 中也都可用。实验显示：

- **只在启动时读取。** 在脚本里设置 `process.env.NODE_USE_ENV_PROXY = '1'` 没有任何效果。要在运行时启用，请调用 `http.setGlobalProxyFromEnv()`（[24.14.0 和 25.4.0 中加入](https://nodejs.org/api/http.html#httpsetglobalproxyfromenvproxyenv)）；它让 `fetch` 和 `https.get` 都走了代理。
- **只设置 HTTP_PROXY。** 只设置 `HTTP_PROXY` 时，`fetch` 会把它用于 `https://` URL，但 `https.get` 会直连。请设置 `HTTPS_PROXY`。
- **NO_PROXY。** `target.test`、`.test`、`*.test`、`*`、`target.test:8443` 以及小写的 `no_proxy` 都绕过了代理；`target.test:9999` 没有绕过，因为端口不同。未设置 `NO_PROXY` 时，发往 `localhost` 和 `127.0.0.1` 的请求会走代理；设置 `NO_PROXY=localhost,127.0.0.1` 后它们改为直连。

Node 26 的 `fetch` 与实验目标协商出了 HTTP/2，在 CONNECT 隧道内也是如此，而 Node 24 用的是 HTTP/1.1。两种情况下代理看到的都是同样的 `CONNECT` 行，也没有请求因此失败。

## Vercel CLI、Corepack、Redocly 等你无法控制的工具

核对日期：2026 年 11 月 7 日。

- **Vercel CLI。** [vercel#17629](https://github.com/vercel/vercel/issues/17629) 仍未关闭：只要设置了任意代理变量，59.18.0 和 59.23.1 版本在 Node 26.8.2 上就会因内置的 undici 5.29.0 以 `invalid onError method` 失败，在 Node 24.21.0 上则正常。修复 [vercel#17634](https://github.com/vercel/vercel/pull/17634) 尚未合并。在有版本包含该修复之前，请用 Node 24 运行这个 CLI。
- **Corepack。** [corepack#834](https://github.com/nodejs/corepack/issues/834) 报告了内置的 undici 6 `ProxyAgent` 在 Node 26.0.0 和 26.1.0 上失败。[Corepack 0.35.0](https://github.com/nodejs/corepack/releases/tag/v0.35.0) 去掉了 undici，改用 Node 的代理支持，所以请设置 `NODE_USE_ENV_PROXY=1`；issue 作者确认了这个组合可用。我们安装的 Node 26.13.0 压缩包里没有 `corepack` 可执行文件（24.22.2 里有），所以请用 `corepack --version` 检查你安装的版本。
- **Redocly CLI。** [redocly-cli#3232](https://github.com/Redocly/redocly-cli/issues/3232) 涉及 `login`、`push` 以及其他 Reunite 命令。[2.62.2](https://github.com/Redocly/redocly-cli/releases/tag/%40redocly/cli%402.62.2) 修复了它们在 Node 26 上的问题，并改用 undici 7.30.0。
- **Serverless Framework。** [4.44.0](https://github.com/serverless/serverless/releases/tag/sf-core%404.44.0) 修复了 npm 包在 Node 26 上通过代理下载的问题（[#13918](https://github.com/serverless/serverless/pull/13918)），[4.45.0](https://github.com/serverless/serverless/releases/tag/sf-core%404.45.0) 修复了它在 Node 26 上通过代理发出的 API 请求。
- **客户端库，不涉及代理。** [SocratiCode#154](https://github.com/giancarloerra/SocratiCode/issues/154)：1.19 之前的 `@qdrant/js-client-rest` 会把 undici 6 的 `Agent` 传给内置 `fetch`，1.19.0 改用了 undici 7。[almyty#702](https://github.com/almyty-inc/almyty/pull/702)：一个来自 undici 6.27.0、带 SSRF 绑定的 `Agent` 在 Node 26.9.0 上失败，它自定义的 DNS 查询从未执行；修复把 undici 升到了 ^8.11.0。[career-ops#4808](https://github.com/career-ops-hq/career-ops/issues/4808)：使用 undici 6 `EnvHttpProxyAgent` 的代理出口在 Node 26 上失败；v1.36.0 让它搭配 undici 自己的 `fetch`，与更早的 [#4787](https://github.com/career-ops-hq/career-ops/pull/4787) 思路相同。

对于其他工具：先升级；在此之前用 Node 24 运行，并在报告问题时附上错误的 `cause` 和第一行 `at`。

## 确认请求确实经过代理

状态码 200 并不能证明响应是代理送来的：被忽略的全局 dispatcher 或匹配上的 `NO_PROXY` 都会让请求直连，而可访问的网站照样返回 200。先看代理自己的日志；在实验中，每个走代理的请求都会留下一行带主机和端口的 `CONNECT`，绕过代理的请求则一行都没有。如果看不到代理日志，就问一个返回你 IP 的服务：

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

用你的代理配置运行一次，例如设置 `HTTPS_PROXY` 并使用 `node --use-env-proxy`，再不带代理运行一次。如果两次输出的 IP 相同，说明请求绕过了代理。

## 常见问题

### undici 7 会在 Node.js 26 上导致 “invalid onError method” 吗？

在我们的测试中不会。我们试过的所有 7.x 版本（7.0.0 到 7.30.2）的 `Agent` 和 `ProxyAgent` 按请求传入时都正常，7.30.2 的 `EnvHttpProxyAgent` 也一样。7.27.0 之前的 7.x 中的 `setGlobalDispatcher()` 会被悄无声息地忽略。

### 为什么在 Node 24 上正常，在 Node 26 上失败？

Node 24.22.2 内置 undici 7.30.1，它接受 undici 5 和 6 的 dispatcher。Node 26.13.0 内置 undici 8.12.2，它只传入新回调。

### NODE_USE_ENV_PROXY=1 能解决吗？

只有在删掉旧 dispatcher 之后才行。只要 undici 6 的 `ProxyAgent` 仍被传给 `fetch`，设置了该变量的 Node 26 照样失败。

### 我看到的是 “invalid onRequestStart method”，是同一个问题吗？

是的，方向相反：undici 8 的 dispatcher 被传给了较旧 Node 版本线的 `fetch`；我们在 Node 24.22.2 上看到了它。修复 A 在这种情况下同样有效。

## 相关指南

- [修复代理环境下 Node.js 的 TypeError: fetch failed](/zh/guides/fix-node-fetch-failed-proxy)逐层解读错误链中的每种原因，而不只是这一种。
- [在 Node.js fetch 中使用代理](/zh/guides/nodejs-fetch-proxy)介绍如何配置带认证和超时的 `ProxyAgent`。
- [Node.js fetch 忽略 HTTPS_PROXY](/zh/guides/node-fetch-ignores-https-proxy)解释不显式启用时出现的超时。
- [代理环境变量](/zh/guides/proxy-environment-variables)比较 curl、Requests 和 Node 如何读取这些变量。
- [npm 代理设置（英文）](/guides/npm-proxy-settings)让 `npm install undici` 能通过代理完成。

**用真实的运营商 IP 测试这个修复。** ipvolt 的[移动代理](/pricing)是运营商网络中的专用设备，每天 $9 起。把它的 URL 填入 `HTTPS_PROXY`，然后用 `--use-env-proxy` 运行上面的 IP 检查。

## 参考来源与延伸阅读

- [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)

## 相关指南

- [修复 Node.js 代理下的 fetch failed 错误](https://ipvolt.com/zh/guides/fix-node-fetch-failed-proxy.md)
- [在 Node.js fetch 中使用代理](https://ipvolt.com/zh/guides/nodejs-fetch-proxy.md)
- [Node.js fetch 忽略 HTTPS_PROXY：一行修复](https://ipvolt.com/zh/guides/node-fetch-ignores-https-proxy.md)

## 关于 ipvolt

示例使用通用的代理设置，并附上原始技术文档链接。具体产品的行为请向你的服务商确认。ipvolt 销售移动代理；住宅代理即将开放，每 GB $2.40 起。

[阅读英文原文](https://ipvolt.com/guides/node-26-invalid-onerror-method.md)

## 接入你的第一个代理。

移动代理每天 $9 起。住宅代理每 GB $2.40 起，即将开放。

[购买移动代理](https://ipvolt.com/dashboard/mobile)

## 住宅代理等候名单

留下邮箱，住宅代理开放时我们会通知你。

开放体验时仅发一封通知邮件，不发送其他邮件。

[加入等候名单](https://ipvolt.com/zh/guides/node-26-invalid-onerror-method#waitlist-closing)

[隐私政策](https://ipvolt.com/privacy)

