fetch failed: invalid onError method en Node.js 26

En Node.js 26, fetch falla con UND_ERR_INVALID_ARG: invalid onError method si recibe un dispatcher de un undici antiguo. Cómo encontrar esa copia y corregirlo.

En esta página

En Node.js 26, fetch() falla con TypeError: fetch failed y la causa invalid onError method (UND_ERR_INVALID_ARG) cuando recibe un dispatcher creado por un paquete npm undici antiguo: un Agent normal, un agente keep-alive, una protección contra SSRF, el agente que incluye una biblioteca cliente o, lo más habitual, un agente de proxy. La petición nunca sale del proceso. Cada versión, mensaje de error y resultado de abajo procede de una ejecución en laboratorio del 7 de noviembre de 2026 o de la documentación y los issues enlazados.

Solución rápida

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

Si el primer comando imprime 8.x y el segundo muestra undici@5 o undici@6, esa copia es el origen probable. La solución que funcionó con todas las versiones de undici en las dos líneas de Node de nuestras pruebas es importar fetch del mismo paquete que crea el dispatcher: import { fetch, ProxyAgent } from 'undici' (la solución A, más abajo). Si el dispatcher solo existe para leer HTTPS_PROXY, elimínalo y arranca Node con --use-env-proxy.

Probado con Node.js 26.13.0 (undici 8.12.2 integrado) y 24.22.2 (undici 7.30.1 integrado), con npm undici 5.29.0, 6.29.1, 7.30.2 y 8.12.4, en macOS 26.5.1 arm64, el 7 de noviembre de 2026. Los scripts y los 166 casos registrados están en la carpeta del laboratorio.

El error: UND_ERR_INVALID_ARG en err.cause

Esta es la salida completa de la reproducción mínima que aparece más abajo, que pasa un Agent de npm undici 6.29.1 al fetch integrado de Node.js 26.13.0. La ruta del laboratorio está abreviada como /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

La primera línea at nombra la copia de undici que lanzó el error: aquí /lab/u6/node_modules/undici. Con un ProxyAgent, la traza gana una línea, at ProxyAgent.dispatch, y el mensaje es el mismo. El mismo script en Node.js 24.22.2 imprimió 200. El código que captura el error y registra solo err.message imprime simplemente fetch failed; el motivo está en err.cause.

Por qué Node 26 rechaza los dispatchers de undici antiguos

El fetch integrado es una copia de undici incluida en Node, y process.versions.undici dice cuál. La opción dispatcher entrega cada petición al objeto que le pasas, así que dos versiones de undici tienen que ponerse de acuerdo en cómo hablan entre sí. undici 8 renombró los callbacks del handler que llama un dispatcher, por ejemplo onError pasó a ser onResponseError (guía de migración). El fetch de Node 26 pasa un handler solo con los callbacks nuevos. Un dispatcher de undici 5 o 6 busca el onError antiguo, no lo encuentra y lanza el error antes de abrir una conexión. Los dispatchers de undici 7 aceptaron los callbacks nuevos en nuestras pruebas.

La reproducción mínima:

js
import { Agent } from 'undici';

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

Los mismos casos con Agent, ProxyAgent y EnvHttpProxyAgent, pasados por petición al fetch integrado:

npm undiciNode.js 26.13.0 (undici 8.12.2)Node.js 24.22.2 (undici 7.30.1)
5.29.0invalid onError methodfunciona
6.29.1invalid onError methodfunciona
7.30.2funcionafunciona
8.12.4funcionainvalid onRequestStart method

undici 5.29.0 no tiene EnvHttpProxyAgent; todas las demás celdas se cumplieron con los tres tipos de agente. Las versiones antiguas se comportaron igual en Node 26: Agent y ProxyAgent de 5.0.0, 5.28.4, 6.0.0 y 6.21.0 fallaron con invalid onError method, y en las ocho versiones 7.x que probamos, de 7.0.0 a 7.28.0, funcionaron. La última fila es el mismo desajuste a la inversa: el fetch de Node 24 pasa un handler sin onRequestStart, y un dispatcher de undici 8 lo rechaza.

Encuentra qué incluye un undici antiguo

Ejecuta los dos comandos con el mismo node y en el mismo proyecto que el código que falla:

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

El primero imprimió 8.12.2 en Node 26.13.0 y 7.30.1 en Node 24.22.2. En un proyecto de prueba con su propio undici 8 y cheerio@1.0.0, el segundo imprimió:

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

El undici@8.12.4 de nivel superior es la copia propia del proyecto. cheerio@1.0.0 trae su propio undici@6.29.1. Una entrada 5.x o 6.x es una sospechosa, no una prueba: solo falla si un dispatcher creado por ella llega al fetch integrado. La traza lo aclara, porque su primera línea at muestra la ruta de la copia que lanzó el error. Una herramienta que compila undici dentro de un único archivo no aparece en npm ls; la traza de Corepack en corepack#834 apunta dentro de corepack.cjs.

Soluciones A y B: fetch y dispatcher del mismo undici

Solución A: usa el fetch del propio undici

Importa fetch del mismo paquete que el dispatcher, para que las dos mitades vengan siempre de una sola versión:

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

En el laboratorio funcionó con undici 5.29.0, 6.29.1, 7.30.2 y 8.12.4 tanto en Node 26.13.0 como en 24.22.2, con Agent y ProxyAgent, y con EnvHttpProxyAgent cuando la versión lo tiene; cada petición por proxy llegó al proxy. Úsala en bibliotecas, CLI y todo lo que se ejecute en más de una línea de Node. Toma FormData y las demás clases de cuerpo del mismo paquete que fetch (documentación de undici). Las correcciones de Serverless y career-ops y el pull request abierto de Vercel CLI enlazados abajo siguen este camino.

Solución B: la misma versión mayor que process.versions.undici

sh
npm install undici@8

Con undici 8.12.4, el fetch integrado de Node 26.13.0 aceptó Agent, ProxyAgent y EnvHttpProxyAgent. En Node 24.22.2 los mismos dispatchers fallaron con invalid onRequestStart method, así que esta solución ata tu código a una sola línea de Node. undici 8 necesita Node.js 22.19.0 o posterior (guía de migración). undici 7.30.2 funcionó en las dos líneas en el laboratorio, y Redocly CLI y el cliente de Qdrant pasaron a undici 7; es una foto de dos versiones de Node, no una promesa sobre la siguiente.

Detrás de un proxy: ProxyAgent, EnvHttpProxyAgent, setGlobalDispatcher

ProxyAgent y EnvHttpProxyAgent de undici 5 o 6 fallan exactamente igual que un Agent normal. Añadir NODE_USE_ENV_PROXY=1 no ayuda mientras tu código siga pasando el dispatcher antiguo: un ProxyAgent de undici 6.29.1 falló con invalid onError method en Node 26.13.0 con la variable definida.

setGlobalDispatcher() falla de forma más silenciosa. En Node 26.13.0, un ProxyAgent global de undici 5.29.0, 6.29.1 o de las versiones 7.x que probamos anteriores a 7.27.0 (de 7.0.0 a 7.26.0), y un EnvHttpProxyAgent global de 6.29.1, no lanzaron ningún error. El fetch integrado los ignoró y se conectó directamente. Contra https://example.com, un ProxyAgent global de undici 6.29.1 obtuvo un 200 mientras el proxy no registraba nada, así que la petición salió desde nuestra propia IP; en Node 24.22.2 el mismo script pasó por el proxy. undici 7.27.0, 7.28.0, 7.30.2 y 8.12.4 funcionaron de forma global. Ninguna de las versiones antiguas escribe Symbol.for('undici.globalDispatcher.2'), donde undici 8 guarda su dispatcher global; 7.27.0 y posteriores sí.

Solución C: deja que Node lea las variables de proxy

Si el dispatcher solo existe para enviar el tráfico por HTTPS_PROXY, elimínalo y deja que lo haga 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, el flag --use-env-proxy y NODE_OPTIONS=--use-env-proxy funcionaron en Node 26.13.0 y 24.22.2, para fetch y para https.get. La opción llegó primero para fetch, en este commit de Node; la documentación de Node recoge NODE_USE_ENV_PROXY desde 24.0.0 y --use-env-proxy desde 24.5.0, y ambos desde 22.21.0. Lo que mostró el laboratorio:

  • Solo se lee al arrancar. Definir process.env.NODE_USE_ENV_PROXY = '1' dentro del script no hizo nada. Para activarlo en tiempo de ejecución, llama a http.setGlobalProxyFromEnv() (añadido en 24.14.0 y 25.4.0); envió por el proxy tanto fetch como https.get.
  • Solo HTTP_PROXY. Con solo HTTP_PROXY definido, fetch lo usó para una URL https://, pero https.get se conectó directamente. Define HTTPS_PROXY.
  • NO_PROXY. target.test, .test, *.test, *, target.test:8443 y no_proxy en minúsculas evitaron el proxy; target.test:9999 no, porque el puerto es distinto. Sin NO_PROXY, las peticiones a localhost y 127.0.0.1 pasaron por el proxy; NO_PROXY=localhost,127.0.0.1 las envió directas.

El fetch de Node 26 negoció HTTP/2 con el destino del laboratorio, también dentro del túnel CONNECT, mientras que Node 24 usó HTTP/1.1. El proxy vio la misma línea CONNECT en ambos casos, y ninguna petición falló por ello.

Vercel CLI, Corepack, Redocly y otras herramientas que no controlas

Comprobado el 7 de noviembre de 2026:

  • Vercel CLI. vercel#17629 está abierto: con cualquier variable de proxy definida, las versiones 59.18.0 y 59.23.1 fallan en Node 26.8.2 con invalid onError method desde un undici 5.29.0 incluido y funcionan en Node 24.21.0. La corrección, vercel#17634, no está fusionada. Hasta que una versión la incluya, ejecuta el CLI con Node 24.
  • Corepack. corepack#834 informa de un ProxyAgent de undici 6 incluido que falla en Node 26.0.0 y 26.1.0. Corepack 0.35.0 abandonó undici y usa el soporte de proxy de Node, así que define NODE_USE_ENV_PROXY=1; quien abrió el issue confirmó que esa combinación funciona. El archivo de Node 26.13.0 que instalamos no trae el binario corepack (el de 24.22.2 sí), así que comprueba qué versión instalaste con corepack --version.
  • Redocly CLI. redocly-cli#3232 cubre login, push y los demás comandos de Reunite. La 2.62.2 los corrigió en Node 26 y pasó a undici 7.30.0.
  • Serverless Framework. La 4.44.0 corrigió la descarga por proxy del paquete npm en Node 26 (#13918), y la 4.45.0 corrigió sus peticiones a la API por proxy en Node 26.
  • Bibliotecas cliente, sin proxy de por medio. SocratiCode#154: @qdrant/js-client-rest anterior a 1.19 pasa un Agent de undici 6 al fetch integrado, y la 1.19.0 pasó a undici 7. almyty#702: un Agent con fijación anti-SSRF de undici 6.27.0 falló en Node 26.9.0 y su lookup de DNS propio nunca se ejecutó; la corrección subió undici a ^8.11.0. career-ops#4808: la salida por proxy con un EnvHttpProxyAgent de undici 6 fallaba en Node 26; la v1.36.0 lo combina con el fetch del propio undici, el mismo enfoque que el anterior #4787.

Para cualquier otra herramienta: actualízala, mientras tanto ejecútala con Node 24 e informa del error con su cause y la primera línea at.

Comprueba que de verdad pasa por el proxy

Una respuesta con estado 200 no demuestra que la haya transportado el proxy: un dispatcher global ignorado o una coincidencia de NO_PROXY envía la petición directa, y un sitio accesible sigue respondiendo 200. Mira primero el propio registro del proxy; en el laboratorio cada petición por proxy dejó una línea CONNECT con el host y el puerto, y las que lo evitaron no dejaron ninguna. Si no tienes acceso al registro del proxy, pregunta a un servicio que devuelva tu IP:

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

Ejecútalo una vez con tu configuración de proxy, por ejemplo con HTTPS_PROXY definido y node --use-env-proxy, y otra sin ella. Si las dos imprimen la misma IP, la petición rodeó el proxy.

Preguntas frecuentes

¿undici 7 provoca «invalid onError method» en Node.js 26?

No en nuestras pruebas. Agent y ProxyAgent de todas las versiones 7.x que probamos, de 7.0.0 a 7.30.2, funcionaron al pasarlos por petición, y también EnvHttpProxyAgent de 7.30.2. setGlobalDispatcher() de 7.x anterior a 7.27.0 se ignoró sin ningún error.

¿Por qué funciona en Node 24 y falla en Node 26?

Node 24.22.2 incluye undici 7.30.1, que aceptó dispatchers de undici 5 y 6. Node 26.13.0 incluye undici 8.12.2, que solo envía los callbacks nuevos.

¿Lo arregla NODE_USE_ENV_PROXY=1?

Solo después de eliminar el dispatcher antiguo. Con un ProxyAgent de undici 6 todavía pasado a fetch, Node 26 falló igual con la variable definida.

Veo «invalid onRequestStart method». ¿Es el mismo problema?

Sí, a la inversa: un dispatcher de undici 8 pasado al fetch de una línea de Node más antigua; lo vimos en Node 24.22.2. La solución A también funciona ahí.

Guías relacionadas

Prueba la solución con una IP real de operador móvil. Un proxy móvil de ipvolt es un dispositivo dedicado en una red de operador, desde US$9 al día. Pon su URL en HTTPS_PROXY y ejecuta la comprobación de IP de arriba con --use-env-proxy.

Fuentes y lecturas adicionales

Referencias técnicas usadas para esta guía. Consulta la documentación de tu versión instalada y la configuración compatible de tu proveedor.