Prevent a single bad item from corrupting long-running n8n executions

Hi everyone,
I’m running a long-running workflow in n8n that processes tens of thousands of items per execution. The workflow runs in queue mode with multiple workers and interacts with external APIs and a database.
Problem I’m Facing
The main issue is poor error boundaries:
• One malformed item causes a Function or DB node to throw
• The entire execution fails, even though most items are valid
• Some items are already written to the database before the failure
• When the workflow retries, I get duplicates or inconsistent state
• Continue On Fail doesn’t give enough control for complex logic
At scale, this makes the workflow unsafe to retry.
// Simulates batch processing in a Function node
return items.map(item => {
if (!item.json.email || !item.json.email.includes(‘@’)) {
// One bad record crashes the whole workflow
throw new Error(Invalid email for user ${item.json.id});
}
return {
json: {
id: item.json.id,
email: item.json.email.toLowerCase()
}
};
});

Describe the problem/error/question

What is the error message (if any)?What’s happens:

• Input: 5,000+ items
• 1 item has an invalid email
• Workflow fails completely
• Valid items after that are never processed
• Retry requires custom deduplication logic

Please share your workflow

(Select the nodes on your canvas and use the keyboard shortcuts CMD+C/CTRL+C and CMD+V/CTRL+V to copy and paste the workflow.)

Share the output returned by the last node

Information on your n8n setup

  • n8n version:
  • Database (default: SQLite):
  • n8n EXECUTIONS_PROCESS setting (default: own, main):
  • Running n8n via (Docker, npm, n8n cloud, desktop app):
  • Operating system:

Hi @Keira_Becky

Totally agree this is exactly what I’ve been running into as well.

In n8n, most nodes fail the entire execution, not just the bad item, so one malformed record can stop everything once you’re processing at scale.

What’s been working for me:
• I stopped throwing errors on batch data and instead catch errors per item and return them as data:return items.map(item => {
try {
if (!item.json.email?.includes(‘@’)) throw new Error(‘Invalid email’);
return { json: { …item.json, status: ‘ok’ } };
} catch (e) {
return { json: { id: item.json.id, error: e.message, status: ‘failed’ } };
}
});

• Then I route status = failed items to a dead-letter table and let the rest continue.
• All DB writes are idempotent (UPSERT), so retries don’t create duplicates.
• I also avoid retrying the whole workflow — only failed items get retried.
• For really large volumes, splitting processing into sub-workflows (one item per execution) has been the closest thing to real error boundaries.

2 Likes

Ohh okay thanks @Niffzy for the reply

This topic was automatically closed 7 days after the last reply. New replies are no longer allowed.