{"slug": "one-bad-row-killed-the-whole-batch-partial-failure-handling-for-n8n-loops", "title": "One Bad Row Killed the Whole Batch: Partial-Failure Handling for n8n Loops", "summary": "A developer running LLM workflows in n8n described how a single malformed lead record halted a nightly batch, leaving 453 valid leads unprocessed until a client noticed the gap. The fix, the developer writes, is to isolate per-item failures inside sub-workflows or error-handling branches, route failed items to a dead-letter list keyed by business ID, report processed/failed/skipped/retried counts separately, and retry only from the dead-letter list to avoid duplicating good records.", "body_md": "*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.*\n\nA 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.\n\nNothing 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.\n\nThe 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.\n\nFirst, 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.\n\nSecond, 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.\n\nThird, 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.\n\nRetrying 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.\n\nThe 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.\n\n*I'm building [AI Automation Lab](https://payhip.com/haopengailab) — production-ready n8n workflow templates. The free [AI Hook Generator](https://payhip.com/b/ulER8) shows how I structure real workflows; my open-source templates live at [github.com/zhp910318/n8n-ai-automation-lab](https://github.com/zhp910318/n8n-ai-automation-lab).*", "url": "https://wpnews.pro/news/one-bad-row-killed-the-whole-batch-partial-failure-handling-for-n8n-loops", "canonical_source": "https://dev.to/haopengai/one-bad-row-killed-the-whole-batch-partial-failure-handling-for-n8n-loops-np9", "published_at": "2026-10-25 21:35:06+00:00", "updated_at": "2026-10-25 21:43:02.086696+00:00", "lang": "en", "topics": ["ai-tools", "mlops", "ai-agents"], "entities": ["n8n", "AI Automation Lab"], "also_reported_by": [], "alternates": {"html": "https://wpnews.pro/news/one-bad-row-killed-the-whole-batch-partial-failure-handling-for-n8n-loops", "markdown": "https://wpnews.pro/news/one-bad-row-killed-the-whole-batch-partial-failure-handling-for-n8n-loops.md", "text": "https://wpnews.pro/news/one-bad-row-killed-the-whole-batch-partial-failure-handling-for-n8n-loops.txt", "jsonld": "https://wpnews.pro/news/one-bad-row-killed-the-whole-batch-partial-failure-handling-for-n8n-loops.jsonld"}}