Hello! I am having trouble to set up an error workflow when an AI agent tool fails. Because when a tool fails, the AI agent node itself doesn’t fail, so the execution doesn’t fail either. And it seems there are no properties within intermediateSteps that assure whether a tool has failed. Is there any workaround for this?
Hi @xmateusx14
I think this is a limitation of the AI Agent node
Error workflows in n8n are triggered only when a node fails, and the AI Agent node does not fail when a tool fails because tool errors are treated as part of the agent’s reasoning, not as execution errors.
As a solution i think you should handle it manullay , and you can do it with IF node or with code node ( try catch mechanism ) ,
for example , instead of letting tools fail, you can make them always return a structured JSON response like { "success": false, "error": "your error message" }, then let the AI Agent read this output and, after the agent, check the result with an IF node to detect failures and manually route the execution to an error branch
Hey @ayoub_ghozzi
It is really unfortunate n8n has such a limitation. But I’ve managed to think another way to catch tool errors. By setting up the agent structured output parser so the AI model analyzes and returns whether a tool has failed. Sure, it opens up the possibility of hallucinating on this matter as well, but at least it is a workround within n8n.
You say the tool itself can be set to always return a structured output parser, but i see no such option in its settings.
Hey everyone! I found a practical way to handle this.
If you are using an HTTP Request node as a tool for your AI Agent, you can prevent the workflow from crashing by using the ‘Never Error’ toggle.
Here is how to set it up:
-
In the HTTP Request node, go to the Options section
-
Add the Response option
-
Toggle ‘Never Error’ to ON
Normally, if a tool fails, n8n stops the entire execution. By turning on ‘Never Error’, the node stays ‘green’ even if the API returns a 400 or 500 error. The error message is then passed back to the AI Agent as a regular string.
The Agent can then ‘read’ the error (e.g., ‘Missing field: email’) and, if instructed in its System Prompt, it can autonomously correct its input and try to call the tool again.
Keep in mind that this will significantly increase the number of interactions, as the agent will use more loops to analyze and fix the errors autonomously.
@Scribble’s “Never Error” tip is solid for HTTP Request tools. for other tool types (Code node, sub-workflow tools, etc.) a similar pattern works: wrap the tool logic in a try/catch inside a Code node and return a structured error object instead of throwing:
js
try {
// your tool logic here
return [{ json: { success: true, result: ... } }]
} catch (e) {
return [{ json: { success: false, error: e.message } }]
}
```
then in your system prompt tell the agent: "if a tool returns `success: false`, read the `error` field and either retry with corrected input or inform the user what went wrong."
this way the agent stays in control of error recovery rather than the whole workflow crashing — and you can still detect failures downstream by checking `intermediateSteps` for any result where `success` is false.
This is a classic ‘silent failure’ problem with AI agents. @Scribble’s advice for the HTTP Request node is great, but for more complex tools like sub-workflows or the Code node, you’ve got to build ‘resilience by design.’
The best way I’ve found to handle this is to treat your tools like they are always returning a response, even when they fail. Wrap your sub-workflow or Code node logic so it always returns a structured object:
Then—and this is the key—in your System Prompt, explicitly tell the agent:
‘If a tool returns { success: false }, do not stop. Read the error field, attempt to fix the input (e.g., correct a date format or missing field), and retry the tool. If you cannot fix it after 2 attempts, explain the technical error to the user.’
This keeps the agent in the driver’s seat. If you need to trigger a global error workflow for logging or alerts, you can use an IF node immediately after the AI Agent node to check the or the final output for that flag and route it accordingly.
Ugh, my bash-brain just ate the code block in that last reply. Let’s try that again so you can actually see it:
Point being: if you return a JSON object with a success flag, the agent sees the error as data rather than a crash, and it can actually try to recover. You can also use an IF node after the agent to check intermediateSteps for any ‘success: false’ if you want to trigger a separate alert.
Third time is the charm. My bash shell keeps interpreting the code block. Here is the actual JS pattern:
try {
// Your tool logic here
return { success: true, data: result };
} catch (error) {
// Don't let the node fail, return the error to the agent
return { success: false, error: error.message };
}
(Replace [code] with backticks). If the agent sees { success: false }, it can use its reasoning to retry or fix. This prevents the ‘silent’ workflow stall because the node itself never actually crashes.
¡Oye @xmateusx14!
Me encontré con esto desde un ángulo diferente: mi agente omitió completamente su herramienta en lugar de que fallara. Adjunto una calculadora, el prompt decía explícitamente que no calculara internamente. Lo hizo de todas formas. Respuesta correcta, nodo verde, intermediateSteps vacío.
El analizador de salida estructurada funciona, pero le estás pidiendo al modelo que reporte su propio fallo. Opté por lo determinista. Construí un nodo Code que colocas después del agente. Ocho verificaciones incluyendo vacío, rechazo, JSON defectuoso, claves faltantes, marcadores de posición, truncamiento, eco de prompt y herramientas requeridas que nunca aparecen en intermediateSteps. Añade contractOk y contractFailures a cada elemento, para que ramifiques en un nodo IF. Sin IA ni dependencias.
Solo detecta fallos mecánicos, no respuestas incorrectas. Si tienes una salida real que rompe una verificación, envíamela y así es como encontré un falso positivo en mi propia regla de truncamiento.
¡Ananya! ![]()
Hay un tercer caso que se sitúa entre los dos modos de fallo en este hilo, y pasa todas las comprobaciones descritas hasta ahora: la herramienta se ejecutó, devolvió éxito y no devolvió nada.
La mía era un paso de recuperación que alimentaba un agente. Un nodo de filtro aguas abajo tenía una condición obsoleta de una corrección anterior, y redujo 8 filas recuperadas correctamente a 0. La llamada a la herramienta en sí fue correcta. Devolvió éxito con un array vacío.
El patrón try/catch ve éxito. El objeto estructurado ve éxito. Y la comprobación de contrato ve la herramienta presente en intermediateSteps, así que también la aprueba allí. El agente entonces hizo la cosa completamente razonable con un conjunto de resultados vacío y dijo que no tenía esa información. Todo en verde, nada lanzado, y el resultado fue un rechazo educado, bien formado y completamente incorrecto.
Lo que añadiría al patrón que la gente está describiendo aquí: devuelve el recuento, no solo el indicador.
try {
const rows = await lookup(q);
return { success: true, count: rows.length, data: rows };
} catch (e) {
return { success: false, error: e.message };
}
Luego el nodo IF comprueba count === 0 así como success === false. Cero es una respuesta legítima a veces, así que no fallas de forma contundente, registras y observas la tasa. Una herramienta de recuperación que se mueve silenciosamente de 5 por ciento vacío a 100 por ciento vacío está rota de una manera que se ve idéntica a la prudencia.
@Ananya_p_kumar sobre tu advertencia de que solo detecta fallas mecánicas y no respuestas incorrectas: creo que «vacío cuando no debería estar vacío» es el único segmento de respuesta incorrecta que es mecánicamente detectable, siempre que la herramienta reporte un recuento. Podría valer la pena una novena comprobación, un indicador de herramienta requerida devuelto cero filas, para herramientas que se declaran a sí mismas como devolviendo colecciones. Feliz de enviarte un ejemplo sanitizado si es útil.
Lo que realmente lo encontró para mí, sin embargo, y lo que recomendaría por encima de cualquier nodo único: mantén un puñado de entradas donde ya conoces la respuesta correcta, y ejecútalas contra producción según un cronograma. Los registros te dicen que la máquina se ejecutó. Las respuestas conocidas te dicen que fue correcta.
Buena observación phantomTool pregunta si la herramienta apareció en intermediateSteps, no si devolvió algo, así que una llamada exitosa que devuelve una colección vacía se ve idéntica a una que devuelve diez filas. El enfoque impulsado por contrato es lo que hace que el noveno check sea viable: declara qué herramientas devuelven colecciones, marca cero solo para esas. Necesita que la herramienta reporte un recuento, así que depende de tu patrón count: rows.length. Abrí un issue para esto: emptyCollection check for tools that return collections · Issue #1 · Ananyapkumar/agent-contract · GitHub y un ejemplo sería muy bienvenido. Y tomando tu punto de que vacío cuando no debería serlo es la porción mecánicamente detectable de lo incorrecto, yo había trazado esa línea demasiado ampliamente.
El punto de Adam13y es el que vale la pena desarrollar — éxito/fracaso no es la forma correcta para un resultado de herramienta. Lo que funciona mejor es hacer que cada herramienta devuelva {status, count, data}, donde status es uno de ok / empty / degraded / failed, luego hacer aserciones sobre esos después del agente en lugar de dentro de una herramienta.
La parte que generalmente se omite: para que el flujo de error se active en absoluto, ese nodo de aserción tiene que lanzar una excepción realmente. Un agente que devolvió una respuesta incorrecta con confianza es una ejecución fallida, y n8n solo la tratará como tal si algo posterior lanza una excepción.
Se acordó la forma, y la parte de lanzamiento es donde cometí el error la primera vez. Intenté hacer que la aserción lanzara en vacío y fue miserable. El vacío es a menudo legítimo: nadie preguntó por algo en el corpus, la recuperación correctamente devuelve nada, el agente correctamente dice que no sabe. Lanzar en eso y te llamas a ti mismo a diario y dejas de leer tus propias alertas dentro de una semana. Lo que funcionó fue dividirlo. ¿Esta ejecución está mal: lanzar, pero solo en un límite destructivo, donde un resultado vacío está a punto de alimentar una escritura o un envío o un pago? ¿La tasa está mal: no lanzar, monitorear. Un paso de recuperación que se queda en 4 por ciento vacío durante meses que va a 100 por ciento está roto, y ninguna ejecución única en esa ventana se ve diferente de una no coincidencia legítima. Eso es lo que atrapó mi error de filtro, y lanzar nunca podría haber funcionado, porque cada ejecución era individualmente defendible. Lanzar también marca la ejecución como fallida, lo que contamina tu tasa de errores y puede desencadenar reintentos que vuelven a ejecutar efectos secundarios. Vale la pena pagar solo donde la acción es destructiva.
La objeción de reintento es la parte en la que discreparía, porque es reparable en lugar de un impuesto permanente. Lanzar una excepción solo es costoso cuando no es seguro ejecutar el reintento dos veces. Clave de idempotencia en los POST salientes, upsert en una clave de negocio en lugar de append, reclamar la fila antes de enviar en cualquier cosa que salga del sistema. Haz eso y reintentar una ejecución fallida deja de ser algo que tengas que sopesar antes de lanzar una excepción, lo que significa que puedes permitirte ser estricto en el límite en lugar de racionarlo.
La otra mitad es que el monitoreo de velocidad necesita volumen, y muchos de estos flujos de trabajo no lo tienen. A 30 ejecuciones por día, un paso de recuperación que se desvía del 4 por ciento vacío al 40 toma más de una semana para diferenciarse del ruido, y ha sido incorrecto todo ese tiempo. Tu idea de respuesta conocida cubre exactamente esa brecha: una entrada canaria en una programación te da una señal en una sola ejecución sin importar cómo se vea el tráfico. No son competidores. La velocidad detecta la deriva lenta donde hay volumen para medir, los canarios la detectan donde no la hay.
Una cosa sobre los canarios. Ejecútalos a través del flujo de trabajo de producción con credenciales de producción, no una copia. Tu bug vivía en un nodo de filtro, y un flujo de trabajo de prueba duplicado habría tenido un filtro limpio y habría pasado cada vez.
Último punto: lo que marca una rama como destructiva tiene que ser una propiedad del flujo de trabajo, no algo que recuerdes. Una etiqueta en la herramienta, o simplemente la regla de que el nodo de aserción se sienta inmediatamente antes de cada nodo de escritura, envío y pago y en ningún otro lugar. De lo contrario, la verificación estricta termina en la ruta que tocaste el día que la construiste.
Justo en el punto del reintento, y creo que tienes razón en que lo estaba tratando como un costo fijo cuando es una decisión de diseño. Clave de idempotencia en salida, upsert en una clave de negocio, reclamar la fila antes de enviar. Haz eso y lanzar excepciones deja de ser algo que racionalices. Tu punto sobre ejecutar canarios a través de producción en lugar de una copia es el que quiero subrayar, porque es más fuerte de lo que parece a primera vista. Un flujo de trabajo duplicado no solo obtiene un filtro limpio. Obtiene credenciales nuevas, su propio presupuesto de límite de velocidad, una caché fría, y cualquier configuración que la persona que lo clonó haya tenido ese día. Terminas probando el diseño en lugar de la cosa desplegada, y la cosa desplegada es la única que sirve a alguien. Una trampa que vale la pena nombrar para cualquiera que construya esto, porque me atrapó y no es obvio. Un canario de respuesta conocida puede pasar mientras la recuperación no devuelve nada en absoluto. Si la pregunta que eliges tiene una respuesta que el modelo ya conoce, responderá correctamente desde sus propios pesos y el canario se pone en verde con un contexto vacío. El canario tiene que hacer una pregunta que sea solo responder desde el corpus. Un hecho interno inventado, una línea de política, un número que no existe en ningún otro lugar. Si una pregunta de conocimiento general puede satisfacerlo, no está probando recuperación, está probando el modelo. En tu último párrafo, que el marcador destructivo tiene que ser una propiedad del flujo de trabajo en lugar de algo que recuerdes. Totalmente de acuerdo, y añadiría que también tiene que fallar ruidosamente cuando está ausente. Encontré uno de estos en mi propio sistema esta mañana. Un paso de autoaprendizaje resguardado detrás de un indicador de configuración. El indicador se estableció en verdadero hace un mes. El archivo que lee nunca había sido generado, por lo que el cargador devolvió vacío, el filtro aguas abajo se convirtió en una no operación, y cada ejecución registró una línea diciendo que no podía leer el archivo y lo estaba ignorando, sin efecto. Nada erró. El indicador decía activado. Durante un mes no hizo nada, y la línea de registro fue honesta enough que había dejado de verla. Que es exactamente tu punto. Una guardia que se degrada silenciosamente a apagado es peor que ninguna guardia, porque dejas de mirar. Si no puede encontrar lo que necesita, debe rehusar ejecutarse en lugar de continuar tranquilamente.
Hola @xmateusx14, desafortunadamente tenemos que usar la solución alternativa. Lo que estoy haciendo es lo siguiente en la imagen de abajo:
Basicamente, en la configuración del agente de IA, estoy haciendo lo siguiente:
Ahora con esta opción obtendrás 2 rutas: “Éxito” y “Error”. Puedes enviar el mensaje de error a Slack o Discord, etc., o pasarlo a otro subproceso que manejará el error y continuará el flujo de trabajo en la ruta de éxito (consulta la primera imagen para ver ambas rutas).
Gracias
Usar Continue (using error output) en el Agente de IA solo crea una rama de error cuando el nodo del Agente lanza una excepción. No convierte un error de herramienta que el agente consumió en una ejecución fallida, por lo que no cubre el caso original.
Mantén el resultado de la herramienta determinista. Devuelve un estado tipificado y un recuento para herramientas de colección. Inmediatamente antes de cualquier efecto secundario externo, verifica el estado que requieres y lanza una excepción si el contrato se incumple. Haz ese efecto secundario idempotente para que un reintento no pueda duplicarlo.
Trata empty (vacío) por separado de failed (fallido). Vacío puede ser válido, así que monitorea su tasa o pruébalo con una canaria de corpus únicamente. Una herramienta fallida, un resultado malformado o una llamada de herramienta requerida faltante no debe dejarse para que el modelo se autodeclare.
Dos cosas que siguen siendo problemáticas después de los comentarios posteriores.
Continue (using error output) solo en el Agent abre una rama de error cuando el nodo Agent en sí mismo lanza una excepción. No convierte un error de herramienta que el agente ya consumió en una ejecución fallida, por lo que el Error Workflow aún nunca se inicia. Ese es el caso original.
La otra brecha es que un Agent verde tiene tres estados diferentes: excepción de herramienta tragada, datos vacíos exitosos y herramienta omitida (intermediateSteps vacío). La salida de herramienta tipada (status + count) más una aseveración inmediatamente antes de una escritura, envío o pago es lo que te permite lanzar una excepción a propósito e finalmente activar el Error Trigger. No lances excepciones en cada recuperación vacía.
Escribí los comentarios posteriores como un how-to, incluyendo webhook ack-first para que un reintento por timeout no ejecute el Agent dos veces: n8n Error Workflow for AI Agent Tool Failures (Why the Agent Stays Green)
Creo n8nChat, una extensión de Chrome/Firefox que coloca un gráfico en el canvas oficial de n8n. No reemplaza el Error Trigger. Útil aquí solo si deseas armar esos dos gráficos en el workflow que ya tienes abierto.

