Setting up error workflow upon AI agent tool failure

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:

  1. In the HTTP Request node, go to the Options section

  2. Add the Response option

  3. 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.

Hey @xmateusx14
Bin von einer anderen Seite drauf gestoßen – mein Agent hat sein Tool komplett übersprungen, statt dass das Tool fehlgeschlagen wäre. Calculator anbei, der Prompt hat explizit gesagt, nicht intern zu berechnen. Es hat es trotzdem gemacht. Richtige Antwort, grüner Node, leere intermediateSteps.

Der structured output parser funktioniert, aber du fragst das Modell, seinen eigenen Fehler zu melden. Ich bin stattdessen deterministisch vorgegangen. Habe einen Code-Node gebaut, den du nach dem Agent einfügst. Acht Checks inklusive leer, Verweigerung, ungültiges JSON, fehlende Keys, Platzhalter, Kürzung, Prompt-Echo und erforderliche Tools, die nie in intermediateSteps auftauchen. Fügt contractOk und contractFailures zu jedem Item hinzu, sodass du auf einem IF-Node verzweigst. Kein AI genutzt oder Dependencies.

Fängt nur mechanische Fehler, keine falschen Antworten. Wenn du echte Ausgaben hast, die einen Check brechen, send sie – so hab ich einen false positive in meiner eigenen Kürzungsregel gefunden.

Ananya :slight_smile:

Es gibt einen dritten Fall, der zwischen den beiden Fehlermodi in diesem Thread liegt, und er besteht jeden bisher beschriebenen Check: Das Tool wurde ausgeführt, hat erfolgreich zurückgegeben und hat nichts zurückgegeben.

Meiner war ein Abrufschritt, der einen Agent fütterte. Ein Filterknoten downstream davon hatte eine veraltete Bedingung aus einer früheren Fehlerbehebung, und er reduzierte 8 korrekt abgerufene Zeilen auf 0. Der Tool-Aufruf selbst war in Ordnung. Er gab Erfolg mit einem leeren Array zurück.

Das Try/Catch-Muster sieht Erfolg. Das strukturierte Objekt sieht Erfolg. Und der Contract-Check sieht das Tool in intermediateSteps, also besteht es auch dort. Der Agent machte dann das völlig vernünftige Ding mit einem leeren Ergebnis und sagte, er habe diese Information nicht. Überall grün, nichts geworfen, und die Ausgabe war eine höfliche, wohlgeformte, völlig falsche Ablehnung.

Was ich zu dem Muster hinzufügen würde, das die Leute hier beschreiben: Geben Sie die Anzahl zurück, nicht nur das Flag.

try {

const rows = await lookup(q);

return { success: true, count: rows.length, data: rows };

} catch (e) {

return { success: false, error: e.message };

}

Dann prüft der IF-Knoten count === 0 sowie success === false. Null ist manchmal eine legitime Antwort, daher verschwinden Sie nicht hart daran, Sie protokollieren es und beobachten die Rate. Ein Abruftool, das stillschweigend von 5 Prozent leer zu 100 Prozent leer wechselt, ist auf eine Weise kaputt, die identisch mit vorsichtig aussieht.

@Ananya_p_kumar zu Ihrem Vorbehalt, dass es nur mechanische Ausfälle und keine falschen Antworten erfasst: Ich denke, „leer, wenn es nicht leer sein sollte

Guter Catch – phantomTool prüft, ob das Tool in intermediateSteps angezeigt wurde, nicht ob es etwas zurückgegeben hat, daher sieht ein erfolgreicher Aufruf, der eine leere Collection zurückgibt, identisch aus mit einem, der zehn Zeilen zurückgibt. Die contract-driven Formulierung ist das, was die neunte Prüfung machbar macht: deklarieren Sie, welche Tools Collections zurückgeben, kennzeichnen Sie null nur für diese. Es braucht das Tool, um eine Anzahl zu melden, also hängt es von deinem count: rows.length-Muster ab. Ich habe ein Issue dafür eröffnet: emptyCollection check for tools that return collections · Issue #1 · Ananyapkumar/agent-contract · GitHub und ein Beispiel wäre sehr willkommen. Und ich verstehe deinen Punkt, dass leer, wenn es nicht sein sollte, der mechanisch erkennbare Teil des Falschen ist – ich hatte diese Linie zu breit gezogen.

Adam13y’s point ist der Punkt, auf dem es sich lohnt zu bauen — success/failure ist die falsche Form für ein Tool-Ergebnis. Was besser funktioniert, ist es, jedes Tool {status, count, data} zurückgeben zu lassen, wobei status einer von ok / empty / degraded / failed ist, und dann diese nach dem Agent zu überprüfen, statt innerhalb eines Tools.

Der Teil, der normalerweise übersprungen wird: Damit der Error-Workflow überhaupt abläuft, muss dieser Assertion-Node tatsächlich einen Fehler werfen. Ein Agent, der eine sichere falsche Antwort zurückgab, ist eine fehlgeschlagene Ausführung, und n8n wird es nur dann als solche behandeln, wenn etwas nachgelagert einen Fehler wirft.

Wir einigten uns auf die Form, und der Werfteil war der Punkt, wo ich das erste Mal falsch lag. Ich versuchte, die Assertion bei leeren Ergebnissen zu werfen, und das war furchtbar. Leer ist oft legitim: niemand fragte nach etwas im Korpus, der Abruf gibt korrekt nichts zurück, der Agent sagt korrekt, dass er es nicht weiß. Wenn man das wirft, ruft man sich selbst täglich auf und hört auf, seine eigenen Warnungen nach einer Woche zu lesen. Was funktionierte, war es zu splitten. Ist DIESE Ausführung falsch: werfen, aber nur an einer destruktiven Grenze, wo ein leeres Ergebnis gleich in einen Schreibvorgang, Versand oder eine Zahlung fließt. Ist die RATE falsch: nicht werfen, überwachen. Ein Abrufsschritt, der monatelang bei 4 Prozent leer liegt und dann auf 100 Prozent springt, ist fehlerhaft, und keine einzelne Ausführung in diesem Fenster sieht anders aus als ein legitimer Nichtabgleich. Das ist es, was meinen Filterfehler entdeckt hat, und Werfen hätte das nie gekonnt, weil jede Ausführung individuell zu verteidigen war. Werfen markiert auch den Durchlauf als fehlgeschlagen, was deine Fehlerquote verschmutzt und Wiederholungen auslösen kann, die Nebenwirkungen erneut ausführen. Es lohnt sich nur dort zu zahlen, wo die Aktion destruktiv ist.

Der Einwand zum Retry ist der Teil, bei dem ich widersprechen würde, denn er ist behebbar statt eine permanente Gebühr. Throwing ist nur teuer, wenn der Retry nicht sicher zweimal laufen kann. Idempotency Key auf ausgehenden POSTs, Upsert auf einem Business Key statt Append, Zeile-vor-dem-Senden-beanspruchen bei allem, das das Haus verlässt. Mach das und das Neu-Ausführen einer fehlgeschlagenen Execution hört auf, etwas zu sein, das du vor dem Throw abwägen musst, was bedeutet, dass du es dir leisten kannst, an der Grenze streng zu sein statt es zu rationieren.

Die andere Hälfte ist, dass Rate Monitoring Volumen braucht, und viele dieser Workflows haben es nicht. Bei 30 Executions pro Tag dauert es besser als eine Woche, bis eine Abrufschritte von 4 Prozent leer zu 40 von Rauschen zu trennen ist, und die ganze Zeit über war es falsch. Deine Known-Answer Idee deckt exakt diese Lücke ab: ein Kanarienvogel Input auf einem Schedule gibt dir ein Signal in einem einzigen Lauf egal wie der Traffic aussieht. Sie konkurrieren nicht. Rate erfasst langsamen Drift, wo es Volumen zum Messen gibt, Kanarienvögel erfassen ihn, wo es nicht gibt.

Eine Sache zu Kanarienvögeln allerdings. Führe sie durch den Production Workflow mit Production Credentials aus, nicht durch eine Kopie. Dein Bug lebte in einem Filter Node, und ein duplizierter Test Workflow hätte einen sauberen Filter gehabt und würde jedes Mal bestanden haben.

Letzter Punkt: Was immer einen Branch als destruktiv markiert, muss eine Eigenschaft des Workflows sein, nicht etwas, das du dir merkst. Ein Tag auf dem Tool, oder einfach die Regel, dass der Assert Node unmittelbar vor jedem Write, Send und Payment Node sitzt und nirgendwo sonst. Sonst landet die strikte Überprüfung auf welchem Pfad auch immer du gerade Dinge berührt hast, an dem Tag, an dem du es gebaut hast.

Fair beim Retry-Point, und ich denke, du hast recht, dass ich ihn als feste Kosten behandelt habe, obwohl es eine Designentscheidung ist. Idempotency-Schlüssel bei ausgehenden Anfragen, Upsert auf einem Business-Key, Zeile vor dem Versand beanspruchen. Mach das und Fehler werfen ist nicht mehr etwas, das du rationieren musst. Dein Punkt über das Durchführen von Canaries durch die Produktion statt einer Kopie ist derjenige, den ich unterstreichen möchte, denn er ist stärker, als er zunächst aussieht. Ein duplizierter Workflow bekommt nicht nur einen sauberen Filter. Er bekommt frische Anmeldedaten, sein eigenes Rate-Limit-Budget, einen kalten Cache und alle Konfigurationen, die die Person, die ihn geklont hat, an jenem Tag zufällig hatte. Am Ende testest du das Design statt die bereitgestellte Sache, und die bereitgestellte Sache ist die einzige, die jemandem dient. Eine Falle, die es wert ist, für jeden, der das baut, benannt zu werden, denn sie hat mich getroffen und es ist nicht offensichtlich. Ein Canary mit bekannter Antwort kann bestehen, während der Abruf überhaupt nichts zurückgibt. Wenn die Frage, die du wählst, eine Antwort hat, die das Modell bereits kennt, wird es aus seinen eigenen Gewichten korrekt antworten und der Canary wird grün mit einem leeren Kontext. Der Canary muss etwas fragen, das nur vom Corpus beantwortet werden kann. Eine erfundene interne Tatsache, eine Richtlinie, eine Zahl, die es sonst nirgends gibt. Wenn eine allgemeine Wissensfrage es erfüllen kann, testet es nicht den Abruf, es testet das Modell. Zu deinem letzten Absatz, dass die destruktive Markierung eine Eigenschaft des Workflows sein muss und nicht etwas, das du dir merkst. Da bin ich völlig einer Meinung, und ich würde hinzufügen, dass sie auch laut fehlschlagen muss, wenn sie fehlt. Ich habe heute Morgen eine davon in meinem eigenen System gefunden. Ein selbstlernender Schritt hinter einer Config-Flag. Die Flag wurde vor einem Monat auf true gesetzt. Die Datei, die er liest, war nie generiert worden, also gab der Loader leer zurück, der Filter downstream wurde zu einem No-Op, und jeder Lauf protokollierte eine Zeile, die sagte, dass er die Datei nicht lesen konnte und sie ignorierte, keine Auswirkung. Nichts ist fehlgeschlagen. Die Flag sagte an. Einen Monat lang tat sie nichts, und die Log-Zeile war ehrlich genug, dass ich aufgehört hatte, sie zu sehen. Das ist genau dein Punkt. Ein Guard, der stillschweigend zu Off degradiert, ist schlimmer als kein Guard, denn du schaust nicht mehr hin. Wenn er nicht finden kann, was er braucht, sollte er sich weigern zu laufen statt ruhig weiterzumachen.

Hey @xmateusx14 ja, leider müssen wir die Umgehungslösung verwenden. Das mache ich folgendermaßen wie im Bild unten:

also im Grunde in der KI-Agent-Einstellung mache ich es wie folgt:

Mit dieser Option erhältst du 2 Routen: „Erfolg

Die Verwendung von Continue (using error output) auf dem AI Agent erzeugt nur einen Fehlerzweig, wenn der Agent-Knoten selbst einen Fehler wirft. Es konvertiert einen Tool-Fehler, den der Agent konsumiert hat, nicht in eine fehlgeschlagene Ausführung, daher deckt es den ursprünglichen Fall nicht ab.

Halte das Tool-Ergebnis deterministisch. Gebe einen typisierten Status und eine Anzahl für Collection-Tools zurück. Unmittelbar vor jeder externen Nebenwirkung assertiere den Zustand, den du benötigst, und wirf einen Fehler, wenn der Vertrag gebrochen ist. Mache diese Nebenwirkung idempotent, damit ein Retry sie nicht duplizieren kann.

Behandle empty getrennt von failed. Empty kann gültig sein, daher überwache seine Rate oder teste es mit einem Corpus-Only-Canary. Ein fehlgeschlagenes Tool, ein fehlgeformtes Ergebnis oder ein fehlender erforderlicher Tool-Aufruf sollte nicht dem Modell zum Selbst-Bericht überlassen werden.

Zwei Dinge, die nach den späteren Kommentaren noch problematisch sind.

Continue (using error output) auf dem Agent öffnet einen Error-Branch nur, wenn der Agent-Knoten selbst wirft. Es konvertiert keinen Tool-Fehler, den der Agent bereits konsumiert hat, in eine fehlgeschlagene Ausführung, daher startet der Error Workflow immer noch nicht. Das ist der ursprüngliche Fall.

Die andere Lücke besteht darin, dass ein grüner Agent drei verschiedene Zustände sind: verschluckter Tool-Throw, erfolgreiche leere Daten und übersprungenes Tool (intermediateSteps leer). Typisierte Tool-Ausgabe (status + count) plus eine Assertion unmittelbar vor einem Schreib-, Send- oder Zahlungsvorgang ist das, womit du absichtlich werfen und endlich den Error Trigger auslösen kannst. Wirf nicht bei jedem leeren Abruf.

Ich habe die späteren Kommentare als eine Anleitung verfasst, einschließlich Webhook-Ack-First, damit ein Timeout-Retry den Agent nicht zweimal ausführt: n8n Error Workflow for AI Agent Tool Failures (Why the Agent Stays Green)

Ich entwickle n8nChat, eine Chrome-/Firefox-Erweiterung, die einen Graphen auf die offizielle n8n-Leinwand platziert. Sie ersetzt nicht den Error Trigger. Hier nützlich nur, wenn du diese beiden Graphen auf dem Workflow, den du bereits offen hast, gerüst werden sollen.