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.
Opa @xmateusx14
Encontrei isso por um ângulo diferente — meu agente pulou a ferramenta inteira em vez de a ferramenta falhar. Calculadora em anexo, o prompt dizia explicitamente para não calcular internamente. Mesmo assim, calculou. Resposta correta, nó verde, intermediateSteps vazio.
O structured output parser funciona, mas você está pedindo ao modelo que reporte sua própria falha. Eu optei por determinístico. Criei um nó Code que você coloca depois do agente. Oito verificações incluindo vazio, recusa, JSON inválido, chaves faltando, placeholders, truncamento, echo do prompt e ferramentas necessárias que nunca aparecem em intermediateSteps. Adiciona contractOk e contractFailures a cada item, aí você ramifica em um nó IF. Sem IA ou dependências.
Só captura falhas mecânicas, não respostas erradas. Se você tiver uma saída real que quebra uma verificação, manda aí — foi assim que encontrei um falso positivo na minha própria regra de truncamento.
Ananya ![]()
Há um terceiro caso que fica entre os dois modos de falha nesta thread, e passa em todas as verificações descritas até agora: a ferramenta foi executada, retornou com sucesso e não retornou nada.
O meu era uma etapa de recuperação alimentando um agente. Um nó de filtro a jusante dela tinha uma condição obsoleta deixada por uma correção anterior, e reduziu 8 linhas recuperadas corretamente para 0. A chamada da ferramenta em si estava correta. Ela retornou sucesso com um array vazio.
O padrão try/catch vê sucesso. O objeto estruturado vê sucesso. E a verificação de contrato vê a ferramenta presente em intermediateSteps, então passa lá também. O agente então fez a coisa inteiramente razoável com um conjunto de resultados vazio e disse que não tinha essa informação. Tudo verde, nada foi lançado, e a saída foi uma recusa educada, bem formada e completamente errada.
O que eu adicionaria ao padrão que as pessoas estão descrevendo aqui: retorne a contagem, não apenas o sinalizador.
try {
const rows = await lookup(q);
return { success: true, count: rows.length, data: rows };
} catch (e) {
return { success: false, error: e.message };
}
Então o nó IF verifica count === 0 bem como success === false. Zero é uma resposta legítima às vezes, então você não falha com força nela, você registra em log e monitora a taxa. Uma ferramenta de recuperação que silenciosamente passa de 5 por cento vazio para 100 por cento vazio está quebrada de uma maneira que parece idêntica à cautelosa.
@Ananya_p_kumar sobre sua ressalva de que isso só detecta falhas mecânicas e não respostas erradas: acho que “vazio quando não deveria estar vazio” é a única fatia de resposta errada que é mecanicamente detectável, desde que a ferramenta reporte uma contagem. Pode valer a pena um nono verificador, um sinalizador obrigatório de ferramenta retornou zero linhas, para ferramentas que se declaram como retornando coleções. Fico feliz em enviar um exemplo sanitizado se for útil.
O que realmente o encontrou para mim, porém, e que eu recomendaria acima de qualquer nó único: mantenha um punhado de entradas onde você já sabe a resposta correta, e execute-as contra produção em um cronograma. Os logs lhe dizem que a máquina foi executada. Respostas conhecidas lhe dizem que estava correta.
Boa observação phantomTool verifica se a ferramenta apareceu em intermediateSteps, não se ela retornou algo, então uma chamada bem-sucedida retornando uma coleção vazia parece idêntica a uma retornando dez linhas. A estrutura orientada por contrato é o que torna a nona verificação viável: declare quais ferramentas retornam coleções, sinalize zero apenas para essas. É necessário que a ferramenta reporte uma contagem, então depende do seu padrão count: rows.length. Abri uma issue para isso: emptyCollection check for tools that return collections · Issue #1 · Ananyapkumar/agent-contract · GitHub e um exemplo é muito bem-vindo. E considerando seu ponto de que vazio quando não deveria ser é a fatia mecanicamente detectável do erro eu havia traçado essa linha muito amplamente.
O ponto de Adam13y é aquele que vale a pena desenvolver — sucesso/falha é o formato errado para um resultado de ferramenta. O que funciona melhor é fazer cada ferramenta retornar {status, count, data}, onde status é um de ok / empty / degraded / failed, depois fazer as asserções naquelas após o agente em vez de dentro de uma ferramenta.
A parte que geralmente é pulada: para o fluxo de erro disparar, esse nó de asserção tem que realmente lançar uma exceção. Um agente que retornou uma resposta confiante errada é uma execução falhada, e o n8n só a tratará como tal se algo a jusante lançar.
concordou com a forma, e a parte do lançamento é onde errei na primeira vez. Tentei fazer a asserção lançar em vazio e foi miserável. Vazio é frequentemente legítimo: ninguém perguntou sobre algo no corpus, a recuperação retorna corretamente nada, o agente corretamente diz que não sabe. Lançar nisso e você chama a si mesmo diariamente e para de ler seus próprios alertas em uma semana. O que funcionou foi dividir. Esta execução está ERRADA: lançar, mas apenas em um limite destrutivo, onde um resultado vazio está prestes a alimentar uma escrita ou um envio ou um pagamento. A TAXA está errada: não lance, monitore. Uma etapa de recuperação em 4 por cento vazio por meses que vai para 100 por cento está quebrada, e nenhuma execução única naquela janela parece diferente de uma correspondência legítima inexistente. Isso é o que pegou meu bug de filtro, e lançar nunca poderia ter, porque cada execução era individualmente defensável. Lançar também marca a execução como falhada, o que polui sua taxa de erro e pode desencadear novas tentativas que reexecutam efeitos colaterais. Vale a pena pagar apenas onde a ação é destrutiva.
A objeção sobre retry é a parte em que eu discordaria, porque é corrigível em vez de um custo permanente. Lançar uma exceção só é caro quando a retentativa não é segura para executar duas vezes. Chave de idempotência em POSTs de saída, upsert em uma chave de negócio em vez de append, ocupar-a-linha-antes-de-enviar em qualquer coisa que saia do sistema. Faça isso e reexecutar uma execução falhada deixa de ser algo que você precisa pesar antes de lançar a exceção, o que significa que você pode ser rigoroso na fronteira em vez de racionar.
A outra metade é que o monitoramento de taxa precisa de volume, e muitos desses fluxos de trabalho não têm. Em 30 execuções por dia, uma etapa de recuperação desviando de 4 por cento vazio para 40 leva mais de uma semana para se separar do ruído, e esteve errada o tempo todo. Sua ideia de resposta conhecida cobre exatamente essa lacuna: uma entrada canária em um cronograma oferece um sinal em uma única execução, não importa como o tráfego se pareça. Não estão competindo. Taxa captura deriva lenta onde há volume para medir, canárias capturam onde não há.
Uma coisa sobre canárias. Execute-as através do fluxo de trabalho de produção com credenciais de produção, não uma cópia. Seu bug vivia em um nó de filtro, e um fluxo de trabalho de teste duplicado teria tido um filtro limpo e passado todas as vezes.
Último ponto: tudo que marca uma ramificação como destrutiva tem que ser uma propriedade do fluxo de trabalho, não algo que você se lembra. Uma tag na ferramenta, ou apenas a regra de que o nó assert senta imediatamente antes de cada nó write, send e payment e em nenhum outro lugar. Caso contrário, a verificação rigorosa acaba no caminho que você estava tocando no dia em que o construiu.
Justo no ponto sobre retry, e acho que você está certo que eu tratava como custo fixo quando na verdade é uma escolha de design. Chave de idempotência na saída, upsert em uma chave de negócio, reivindicar a linha antes de enviar. Faça isso e o lançamento de exceções deixa de ser algo que você raciona. Seu ponto sobre executar canários através da produção em vez de uma cópia é o que quero destacar, porque é mais forte do que parece à primeira vista. Um workflow duplicado não apenas obtém um filtro limpo. Recebe credenciais novas, seu próprio orçamento de limite de taxa, um cache vazio, e qualquer configuração que a pessoa que o clonou tivesse naquele dia. Você acaba testando o design em vez da coisa implantada, e a coisa implantada é a única servindo alguém.
Uma armadilha que vale a pena nomear para qualquer um construindo isso, porque me pegou e não é óbvio. Um canário de resposta conhecida pode passar enquanto a recuperação não retorna nada. Se a pergunta que você escolhe tem uma resposta que o modelo já conhece, ele responderá corretamente a partir de seus próprios pesos e o canário fica verde com um contexto vazio. O canário tem que perguntar algo que é apenas respondível a partir do corpus. Um fato interno inventado, uma linha de política, um número que não existe em lugar nenhum. Se uma pergunta de conhecimento geral pode satisfazê-lo, não está testando recuperação, está testando o modelo.
No seu último parágrafo, que o marcador destrutivo tem que ser uma propriedade do workflow em vez de algo que você se lembra. Concordo fortemente, e eu acrescentaria que também tem que falhar alto quando está ausente.
Encontrei um desses no meu próprio sistema esta manhã. Uma etapa de auto-aprendizado fechada atrás de um sinalizador de configuração. O sinalizador foi definido como verdadeiro um mês atrás. O arquivo que ele lê nunca havia sido gerado, então o carregador retornou vazio, o filtro a jusante se tornou um no-op, e cada execução registrava uma linha dizendo que não conseguia ler o arquivo e estava ignorando, sem efeito. Nada errored. O sinalizador dizia ativado. Por um mês não fez nada, e a linha de log era honesta o suficiente que eu tinha parado de vê-la.
O que é exatamente seu ponto. Uma proteção que degrada silenciosamente para desativado é pior que nenhuma proteção, porque você para de procurar. Se não conseguir encontrar o que precisa, deve recusar executar em vez de continuar em silêncio.
Opa @xmateusx14 sim, infelizmente temos que usar a solução alternativa, o que estou fazendo é o que está na imagem abaixo:
basicamente nas configurações do agente de IA, estou fazendo assim:
Agora com essa opção você vai ter 2 rotas: “Sucesso” e “Erro”, aí você pode enviar a mensagem de erro para slack ou discord, etc., ou passar para outro sub-workflow que vai tratar o erro e continuar o workflow na rota de sucesso (Por favor, veja a primeira imagem para ver as duas rotas).
Obrigado
Usar Continuar (usando saída de erro) no Agente de IA apenas cria um branch de erro quando o nó do Agente em si lança um erro. Isso não converte um erro de ferramenta que o agente consumiu em uma execução falha, então não cobre o caso original.
Mantenha o resultado da ferramenta determinístico. Retorne um status tipado e uma contagem para ferramentas de coleção. Imediatamente antes de qualquer efeito colateral externo, afirme o estado que você requer e lance um erro se o contrato for quebrado. Torne esse efeito colateral idempotente para que uma tentativa não possa duplicá-lo.
Trate vazio separadamente de falha. Vazio pode ser válido, então monitore sua taxa ou teste-o com um canário só de corpus. Uma ferramenta falha, resultado malformado ou chamada de ferramenta obrigatória ausente não devem ser deixados para o modelo se auto-reportar.

