n8n cae con «JavaScript heap out of memory» en los nodos agente
El contenedor muere a mitad de ejecución y vuelve sin más explicación que una línea del log. Esa línea te dice cuál de dos problemas completamente distintos tienes — y tienen arreglos opuestos.
Una instancia de n8n auto-hospedada que llevaba meses estable empieza a reiniciarse. El flujo es el mismo de siempre, salvo que ahora lleva un nodo agente de IA, y a veces un bucle alrededor de ese nodo. Los logs terminan con una traza que tú no escribiste, y el contenedor vuelve como si nada — hasta la siguiente ejecución.
Lo primero no es arreglarlo. Es averiguar cuál de dos fallos distintos estás mirando, porque el consejo estándar para uno empeora el otro.
Leer el error FATAL de heap en los logs de n8n
Hay dos formas de que un contenedor de n8n muera por memoria, y se parecen desde fuera y no tienen nada que ver por dentro.
<--- Last few GCs --->
[1:0x7f8b2c0] 412039 ms: Mark-sweep 3969.1 (4066.5) -> 3968.3 (4067.2) MB
<--- JS stacktrace --->
FATAL ERROR: Reached heap limit Allocation failed - JavaScript heap out of memory$ docker inspect n8n --format '{{.State.ExitCode}} {{.State.OOMKilled}}'
137 trueLa distinción decide tu siguiente movimiento. En el caso A al proceso le quedaba memoria según el sistema operativo, pero V8 se negó a crecer más allá del límite de old-space que tiene configurado. En el caso B el contenedor tocó el límite de memoria de su cgroup y lo terminaron — sin aviso, sin traza, exit 137.
Por qué los nodos agente retienen memoria
n8n mueve los datos entre nodos como un array de items en memoria. Ese diseño es lo que hace que el editor se sienta inmediato —puedes inspeccionar la salida de cualquier nodo cuando quieras— y es también por lo que el perfil de memoria de un flujo es más o menos la suma de todo lo que ha producido cada nodo, no el tamaño de lo que está procesando el nodo actual.
Un nodo agente apila varios multiplicadores encima de eso:
- Memoria de conversación. Una ventana de búfer mantiene vivos los últimos N intercambios para que el agente tenga contexto. N cuenta turnos, no bytes — una ventana de 10 es pequeña con mensajes cortos y enorme en cuanto una herramienta empieza a devolver documentos.
- Las salidas de las herramientas entran en el contexto. Todo lo que devuelve una herramienta se añade a la conversación para que el modelo pueda razonar sobre ello. Una herramienta que devuelve la respuesta completa de una API mete esa respuesta entera en memoria, y la deja ahí el resto de la ejecución.
- La salida de los sub-nodos se retiene. Cada sub-nodo colgado del agente guarda su propia salida para poder inspeccionarla en el editor, así que los pasos intermedios no desaparecen cuando el agente sigue adelante.
- Los datos binarios van a memoria por defecto. Si no pones el modo de datos binarios en filesystem o S3, los adjuntos y las descargas viven en el heap junto a todo lo demás.
- Los bucles multiplican todo lo anterior. Ejecutar el agente una vez por fila de una hoja de 500 filas no son 500 ejecuciones pequeñas: es una ejecución que acumula 500 contextos de agente.
Por eso la caída correlaciona con el volumen de datos y no con la complejidad del flujo, y por eso aparece semanas después de haberlo construido: la lógica no cambió, las entradas simplemente crecieron.
Bucles de reinicio en auto-hospedado
El reinicio es lo que convierte una ejecución mala en una tarde mala. Con una política de reinicio puesta, el contenedor vuelve, n8n retoma lo que puede, y si el dato que lo disparó sigue encolado vuelve a correr la misma ejecución condenada. Tres o cuatro ciclos después, el cuadro es un servicio que técnicamente está arriba y no está haciendo nada.
Cosas que conviene poner antes de tocar nada arquitectónico — ninguna es un arreglo, son la forma de dejar de perder información sobre el fallo:
| Ajuste | Qué hace | Por qué importa aquí |
|---|---|---|
| N8N_DEFAULT_BINARY_DATA_MODE=filesystem | Saca los adjuntos del heap | Elimina el mayor contribuyente único en flujos con ficheros |
| EXECUTIONS_DATA_SAVE_ON_SUCCESS=none | Deja de guardar el payload de las ejecuciones correctas | Encoge la base de datos y lo que se retiene durante la ejecución |
| EXECUTIONS_DATA_PRUNE=true | Caduca los datos de ejecuciones viejas | Corta una fuga lenta que parece una fuga de memoria |
| EXECUTIONS_MODE=queue | Las ejecuciones corren en workers aparte | Una caída mata un worker, no el editor y los demás flujos |
| Límite del contenedor ≥ límite de heap + margen | Alinea los dos techos | Convierte las muertes silenciosas por exit 137 en errores de JS legibles |
Esa última fila es la que todo el mundo se salta. Si el contenedor está limitado a 2 GB y V8 está configurado para 4 GB, V8 no llegará nunca a su propio límite — el kernel llega antes, y tú no ves jamás el error FATAL que te habría contado lo que pasaba.
Estado externo y contexto por agente
Todas las mitigaciones de arriba van de sobrevivir a la acumulación. El arreglo estructural es no acumular: el contexto del agente deja de ser una variable dentro de un proceso y pasa a ser un registro fuera de él.
Eso cambia lo que significa una caída. Cuando el historial de conversación, los resultados intermedios y las salidas de herramientas viven en un almacén externo, el proceso que atiende un paso solo sostiene ese paso. Se le puede reiniciar, mover a otra máquina o correr a la vez que otros cien, porque no hay nada dentro que merezca la pena conservar.
También cambia lo que significa un bucle. Quinientas filas pasan a ser quinientas unidades de trabajo independientes con su contexto aislado, en vez de una ejecución cuya huella de memoria crece con cada fila. El fallo deja de ser todo o nada: la fila 340 falla, las filas 1 a 339 ya están hechas, y la 340 se reintenta sola.
Así corren los agentes en Bentho: contexto persistente por agente en estado externo, ejecutado por workers que sostienen una unidad de trabajo cada vez. No es un parámetro de tuning, y ese es justo el punto: no hay ningún valor de --max-old-space-size que haga que un diseño en proceso deje de acumular.
Ruta de migración para los flujos críticos
Nadie debería migrar de golpe una instancia de n8n que funciona, y no hace falta. Los flujos que caen son un subconjunto pequeño —normalmente los que tienen un agente dentro de un bucle— y son los que vale la pena mover primero.
- Confirma qué fallo tienes. Mira el código de salida y si OOMKilled es true. Todo lo que viene después depende de esa respuesta.
- Compra margen por la vía barata. Datos binarios a filesystem, datos de ejecución podados, modo cola activado. Esto muchas veces para los reinicios del todo, y siempre te da sitio para trabajar.
- Encuentra el nodo que acumula. Casi siempre es un agente, un bucle, o un nodo HTTP que devuelve algo mucho más grande de lo que nadie supuso. Registra el número de items y el tamaño del payload entre nodos.
- Mueve ese flujo, solo ese. El agente corre como worker de Bentho con estado externo; n8n se queda con los disparadores y las integraciones, que es en lo que es bueno, y llama al worker en vez de hospedar el agente.
- Verifica con la carga que lo rompió. Reproduce el volumen real, no una fila de prueba. Un flujo que sobrevive a una fila nunca fue el problema.
- Deja el resto en paz. Los flujos que nunca cayeron no necesitan plataforma nueva.
El paso uno cuesta dos minutos y descarta la mitad de los consejos de internet. Empieza ahí, aunque hoy no hagas nada más.
Preguntas frecuentes
¿Qué significa FATAL ERROR: Reached heap limit Allocation failed - JavaScript heap out of memory en n8n?
Significa que el proceso de Node.js agotó el heap que V8 tenía configurado y se detuvo a sí mismo. En n8n suele querer decir que una ejecución acumuló en memoria más datos de los que permite el límite — habitualmente un agente de IA dentro de un bucle, o datos binarios retenidos en el heap. No es un fallo de la lógica de tu flujo: es la cantidad de datos que la ejecución sostiene a la vez.
¿El exit code 137 es el mismo problema?
No, y distinguirlos importa. Un exit 137 con OOMKilled=true significa que el kernel mató el contenedor por pasarse de su límite de memoria — no hay traza porque nadie se la pidió al proceso. El FATAL ERROR de heap es V8 deteniéndose solo. Subir --max-old-space-size ayuda al segundo y empeora el primero.
¿Lo arregla poner más RAM?
Lo aplaza. Más RAM sube el techo, y una ejecución cuya memoria crece con el tamaño de su entrada acaba llegando a cualquier techo. Es una medida de emergencia razonable y un mal plan.
¿El modo cola resuelve el problema de memoria de los agentes?
Lo contiene. Las ejecuciones corren en workers separados, así que una mala mata un worker en vez de llevarse por delante el editor y todos los demás flujos. La ejecución que acumula de más sigue muriendo — lo que deja de hacer es arrastrar a todo lo demás.
¿Por qué empezó a pasar sin haber cambiado el flujo?
Porque el uso de memoria sigue al volumen de datos, no a la complejidad del flujo. Un flujo construido contra 20 filas y un historial de conversación corto se comporta de forma completamente distinta contra 2.000 filas y salidas de herramientas que fueron creciendo.