This is part of my build-in-public series on running LLM workflows in n8n in production. Everything here is from real templates I sell and support β the mistakes are mine, the fixes are tested.
A nightly workflow of mine processed a batch of new leads: enrich each one, score it, write it to the CRM. One night a single lead had a malformed phone field, the enrichment node threw, and the execution stopped. The other 453 leads sat unprocessed until the next morning, when a client asked why half the day's leads were missing.
Nothing about this was exotic. A batch loop that stops on the first error is the default behavior in most workflow tools, and it turns one bad record into a silent outage for every record behind it. The error itself was visible β the execution log showed the failure. What was invisible was the cost: hundreds of good records that never got processed because they happened to queue behind a bad one.
The fix is not "validate better." You will never validate away every malformed record from a source you do not control. The fix is to decide, per item, what failure means β and to make that decision before the loop runs, not after it stops.
First, isolate failures to the item that caused them. In n8n this means the per-item work runs where an error cannot kill the loop: a sub-workflow executed per item, or an error-handling branch that catches the failure and returns it as data instead of throwing. The loop itself never sees an exception β it sees a result, either success or a structured failure record. Once a bad row produces a row in a failure list instead of a stopped execution, the batch always completes, and "complete" stops being a lie.
Second, collect failures into a dead-letter list, not a log line. A failed item goes into a table with the item's business key, the error, and the raw payload β everything needed to reprocess it without re-running the whole batch. My rule: a batch that ends with failures still reports success for the good items and a separate count for the failed ones, and the failed list is the input to a retry workflow, not a note for someone to read someday. If a failure cannot be retried from the list alone, the list is missing a field, and that is the bug to fix.
Third, report four numbers, not one status. Processed, failed, skipped as duplicates, and retried. When failed jumps from zero to one, that is a data problem worth one look. When failed jumps to forty, that is a schema change upstream, and I want to know before the client does. A batch that reports only "done" hides exactly the distinction between "everything worked" and "everything after row 47 never ran" β which is the distinction the whole report exists to make.
Retrying the whole batch is how a partial failure becomes a duplicate problem: the good items run twice. So retries run against the dead-letter list only, keyed by the business id, with the same dedupe check the main loop uses. A retried item that succeeds is marked done; one that fails again stays on the list with its new error. Nothing ever re-enters the main batch, and the main batch never re-processes a good item because a bad one showed up late.
The principle is simple: a batch loop's job is to finish the batch, not to stop at the first surprise. One bad row should cost you one row, one entry in a list, and one retry β never the 453 good rows standing behind it.
I'm building AI Automation Lab β production-ready n8n workflow templates. The free AI Hook Generator shows how I structure real workflows; my open-source templates live at github.com/zhp910318/n8n-ai-automation-lab.