fetch failed: invalid onError method в Node.js 26

В Node.js 26 fetch падает с UND_ERR_INVALID_ARG: invalid onError method, если ему передан диспетчер из старого undici. Как найти эту копию и что исправить.

На этой странице

В Node.js 26 fetch() падает с TypeError: fetch failed и причиной invalid onError method (UND_ERR_INVALID_ARG), если ему передан диспетчер, созданный старым npm-пакетом undici: обычный Agent, keep-alive-агент, защита от SSRF, агент, встроенный в клиентскую библиотеку, или, чаще всего, прокси-агент. Запрос так и не покидает процесс. Каждая версия, строка ошибки и результат ниже получены на стенде 7 ноября 2026 года или взяты из документации и issues по ссылкам.

Быстрое исправление

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

Если первая команда выводит 8.x, а вторая показывает undici@5 или undici@6, вероятный источник — эта копия. Исправление, которое в наших тестах сработало со всеми версиями undici на обеих линейках Node, — импортировать fetch из того же пакета, что создаёт диспетчер: import { fetch, ProxyAgent } from 'undici' (исправление A ниже). Если диспетчер нужен только для того, чтобы читать HTTPS_PROXY, удалите его и запускайте Node с --use-env-proxy.

Проверено на 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, 7 ноября 2026 года. Скрипты и все 166 записанных случаев лежат в папке стенда.

Ошибка: UND_ERR_INVALID_ARG в err.cause

Это полный вывод минимального примера, показанного ниже: он передаёт Agent из npm undici 6.29.1 во встроенный fetch Node.js 26.13.0. Путь стенда сокращён до /lab:

code
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

Встроенный fetch — это копия undici, встроенная в Node, а process.versions.undici показывает, какая именно. Параметр dispatcher передаёт каждый запрос объекту, который вы указали, поэтому две версии undici должны договориться о том, как общаться друг с другом. В undici 8 переименованы колбэки обработчика, которые вызывает диспетчер, например onError стал onResponseError (руководство по миграции). fetch в Node 26 передаёт обработчик только с новыми колбэками. Диспетчер undici 5 или 6 ищет старый onError, не находит его и выбрасывает ошибку ещё до открытия соединения. Диспетчеры undici 7 в наших тестах новые колбэки приняли.

Минимальный пример:

js
import { Agent } from 'undici';

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

Те же случаи с Agent, ProxyAgent и EnvHttpProxyAgent, переданными во встроенный fetch для отдельного запроса:

npm undiciNode.js 26.13.0 (undici 8.12.2)Node.js 24.22.2 (undici 7.30.1)
5.29.0invalid onError methodработает
6.29.1invalid onError methodработает
7.30.2работаетработает
8.12.4работаетinvalid onRequestStart method

В undici 5.29.0 нет EnvHttpProxyAgent; все остальные ячейки верны для всех трёх типов агентов. Старые выпуски вели себя на Node 26 так же: Agent и ProxyAgent из 5.0.0, 5.28.4, 6.0.0 и 6.21.0 падали с invalid onError method, а из восьми опробованных выпусков 7.x, от 7.0.0 до 7.28.0, работали. Последняя строка — то же несовпадение в обратную сторону: fetch в Node 24 передаёт обработчик без onRequestStart, и диспетчер undici 8 его отвергает.

Как найти, что тянет старый undici

Запустите обе команды тем же node и в том же проекте, где падает код:

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

Первая вывела 8.12.2 на Node 26.13.0 и 7.30.1 на Node 24.22.2. В тестовом проекте с собственным undici 8 и cheerio@1.0.0 вторая вывела:

code
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 — это подозреваемый, а не доказательство: ошибка возникает, только если созданный этой копией диспетчер передаётся во встроенный fetch. Вопрос решает трассировка: её первая строка at показывает путь к копии, которая выбросила ошибку. Инструмент, который вкомпилировал undici в один файл, в npm ls не виден вовсе; трассировка Corepack в corepack#834 указывает внутрь corepack.cjs.

Исправления A и B: fetch и диспетчер из одного undici

Исправление A: fetch из самого undici

Импортируйте 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);

На стенде это сработало с undici 5.29.0, 6.29.1, 7.30.2 и 8.12.4 и на Node 26.13.0, и на 24.22.2, с Agent и ProxyAgent, а также с EnvHttpProxyAgent там, где он есть; каждый запрос через прокси дошёл до прокси. Используйте этот вариант в библиотеках, CLI и всём, что работает больше чем на одной линейке Node. FormData и другие классы тела запроса берите из того же пакета, что и fetch (документация undici). Этим путём пошли исправления Serverless и career-ops и открытый pull request для Vercel CLI, на которые ссылаемся ниже.

Исправление B: мажорная версия, как в process.versions.undici

sh
npm install undici@8

С undici 8.12.4 встроенный fetch Node 26.13.0 принял Agent, ProxyAgent и EnvHttpProxyAgent. На Node 24.22.2 те же диспетчеры упали с invalid onRequestStart method, так что это исправление привязывает код к одной линейке Node. undici 8 требует Node.js 22.19.0 или новее (руководство по миграции). undici 7.30.2 на стенде работал на обеих линейках, а Redocly CLI и клиент Qdrant перешли на undici 7; это снимок двух выпусков Node, а не обещание насчёт следующего.

За прокси: ProxyAgent, EnvHttpProxyAgent, setGlobalDispatcher

ProxyAgent и EnvHttpProxyAgent из undici 5 или 6 падают точно так же, как обычный Agent. NODE_USE_ENV_PROXY=1 не помогает, пока код по-прежнему передаёт старый диспетчер: ProxyAgent из undici 6.29.1 упал с invalid onError method на Node 26.13.0 и с этой переменной.

setGlobalDispatcher() ломается тише. На Node 26.13.0 глобальный ProxyAgent из undici 5.29.0, 6.29.1 или опробованных выпусков 7.x до 7.27.0 (от 7.0.0 до 7.26.0), а также глобальный EnvHttpProxyAgent из 6.29.1 не выбросили ошибку. Встроенный fetch проигнорировал их и подключился напрямую. При запросе к https://example.com глобальный ProxyAgent из undici 6.29.1 получил 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 хранит свой глобальный диспетчер; 7.27.0 и новее записывают.

Исправление C: пусть Node сам читает переменные прокси

Если диспетчер нужен только для того, чтобы отправлять трафик через 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; документация Node указывает 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); он направил через прокси и fetch, и https.get.
  • Только HTTP_PROXY. Когда задан только HTTP_PROXY, fetch использовал его для URL https://, а 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 отправил их напрямую.

fetch в Node 26 договорился с целевым сервером стенда о HTTP/2, в том числе внутри туннеля CONNECT, тогда как Node 24 использовал HTTP/1.1. Прокси в обоих случаях видел одну и ту же строку CONNECT, и ни один запрос из-за этого не упал.

Vercel CLI, Corepack, Redocly и другие инструменты вне вашего контроля

Проверено 7 ноября 2026 года:

  • Vercel CLI. vercel#17629 открыт: при любой заданной переменной прокси версии 59.18.0 и 59.23.1 падают на Node 26.8.2 с invalid onError method из встроенного undici 5.29.0 и работают на Node 24.21.0. Исправление, vercel#17634, не влито. Пока оно не попадёт в выпуск, запускайте CLI на Node 24.
  • Corepack. В corepack#834 описан встроенный ProxyAgent undici 6, падающий на Node 26.0.0 и 26.1.0. Corepack 0.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 касается login, push и других команд Reunite. 2.62.2 исправил их на Node 26 и перешёл на undici 7.30.0.
  • Serverless Framework. 4.44.0 исправил загрузку npm-пакета через прокси на Node 26 (#13918), а 4.45.0 — его запросы к API через прокси на Node 26.
  • Клиентские библиотеки, без прокси. SocratiCode#154: @qdrant/js-client-rest до 1.19 передаёт Agent undici 6 во встроенный fetch, а 1.19.0 перешёл на undici 7. almyty#702: Agent с SSRF-привязкой из undici 6.27.0 падал на Node 26.9.0, и его собственный DNS-lookup так и не вызывался; исправление подняло undici до ^8.11.0. career-ops#4808: выход через прокси с EnvHttpProxyAgent undici 6 падал на Node 26; v1.36.0 использует с ним собственный fetch undici — тот же подход, что и в более раннем #4787.

Для любого другого инструмента: обновите его, пока запускайте на Node 24 и сообщите об ошибке с её cause и первой строкой at.

Как проверить, что запрос действительно идёт через прокси

Ответ со статусом 200 не доказывает, что его доставил прокси: проигнорированный глобальный диспетчер или совпадение с 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 ошибку «invalid onError method» в Node.js 26?

В наших тестах нет. Agent и ProxyAgent из всех опробованных выпусков 7.x, от 7.0.0 до 7.30.2, работали при передаче в отдельный запрос, как и EnvHttpProxyAgent из 7.30.2. setGlobalDispatcher() из 7.x до 7.27.0 игнорировался без всякой ошибки.

Почему на Node 24 всё работает, а на Node 26 падает?

В Node 24.22.2 встроен undici 7.30.1, который принимал диспетчеры undici 5 и 6. В Node 26.13.0 встроен undici 8.12.2, который передаёт только новые колбэки.

Помогает ли NODE_USE_ENV_PROXY=1?

Только после того, как вы удалите старый диспетчер. Пока ProxyAgent undici 6 по-прежнему передавался в fetch, Node 26 падал так же и с этой переменной.

У меня «invalid onRequestStart method». Это та же проблема?

Да, только в обратную сторону: диспетчер undici 8 передан в fetch более старой линейки Node; мы видели это на Node 24.22.2. Исправление A работает и здесь.

Похожие руководства

Проверьте исправление через настоящий IP мобильного оператора. Мобильный прокси ipvolt — это выделенное устройство в сети оператора, от $9 в день. Укажите его URL в HTTPS_PROXY и запустите проверку IP выше с --use-env-proxy.

Источники и дополнительное чтение

Технические материалы, использованные при подготовке руководства. Сверяйтесь с документацией вашей версии и с поддерживаемой конфигурацией вашего провайдера.