DEV Community

Andrey Tverdomed for Entech Solutions

Posted on Originally published at integrationstars.com

Race Conditions in n8n AI Agent Workflows (and the Fix)

Originally published on integrationstars.com.

The short version: check shared state (a calendar, a stock count, a CRM record), then act on it, with nothing stopping a second execution from checking in between. An AI step stretches that window from milliseconds to seconds. The fix is to claim before you act, give every business action an idempotency key, and re-check the real system right before the final write. A better prompt, a stricter model or n8n queue mode will not fix it, and queue mode makes races more likely because it adds parallelism.

Why this keeps happening

A popular n8n appointment-booking workflow shared on LinkedIn this month drew the same comment from at least seven different engineers: what happens when two people ask for the same slot at once? The answer, in almost every check-then-act workflow, is that both get it.

n8n runs every incoming request as its own execution, and executions run in parallel. Execution A reads the calendar: 3pm free. Execution B reads the calendar: 3pm free. A books 3pm. B books 3pm. Each step did exactly what it was built to do. The failure lives in the gap between them, which is why it never shows up in testing, where requests arrive one at a time.

Why the AI step makes it worse

In a plain workflow, check and act might sit a few hundred milliseconds apart. Put an AI Agent node between them (reading the request, deciding which slot fits, calling a tool, maybe confirming with the user) and that gap grows to several seconds, sometimes much longer when there's a conversation in between. The probability of a collision grows with that window. An agent that works flawlessly in a demo can double-book within its first busy hour.

Agents also add their own duplicates: a model may call the same booking tool twice in one run, or a timeout makes the workflow retry a call that actually succeeded. Neither is the model "making a mistake" in a way a better prompt fixes. They're ordinary distributed-systems problems arriving through a new door.

Where duplicate and parallel runs come from

  • Simultaneous requests: two users, two webhook calls, two executions, same resource
  • Webhook redelivery: senders like Stripe, and most webhook providers, resend an event if they don't get a fast enough response
  • Retry On Fail: n8n's node-level retry re-runs a step after a timeout, even if the first attempt actually went through on the other side
  • Agent tool calls: the model calls the same tool twice, or a second agent run picks up the same task
  • Queue mode with multiple workers: more executions in parallel by design

The fix: claim first, key every action, re-check before writing

1. Claim the resource before acting on it

Instead of "check, then book", the workflow first tries to claim the slot in a store that refuses a second claim. Only the execution that wins the claim continues. In Postgres that's a single insert against a unique constraint:

INSERT INTO slot_claims (slot_id, execution_id)
VALUES ('2026-10-08T15:00', 'EXECUTION_ID')
ON CONFLICT (slot_id) DO NOTHING
RETURNING slot_id;
Enter fullscreen mode Exit fullscreen mode

(In n8n, EXECUTION_ID is the expression for the current execution's id.)

If a row comes back, this execution owns the slot. If nothing comes back, someone else got there first, so route to a clean "that time was just taken" response. A Redis key set only if it doesn't already exist does the same job. What matters is that the database or Redis makes the decision atomically, not an IF node comparing two values that may already be stale.

2. Give every business action an idempotency key

Build one ID per real-world action (the booking request ID, the order ID, the webhook event ID), record it before the side effect, and check it first on every attempt. When the webhook resends, the retry fires, or the agent calls the tool again, the second attempt sees the key is already done and stops instead of booking or charging twice. Where the downstream API accepts an idempotency key of its own (Stripe does), pass it through as well.

3. Re-check the real system right before the final write

Your claim table protects against other runs of this workflow. It doesn't know about someone booking the same slot directly in the calendar app. A last read immediately before the write, and a clear failure path if the slot is gone, covers what the claim can't.

Let the agent decide, not commit. The cleanest structure keeps the AI out of the critical section entirely: the agent figures out what the user wants, then hands a structured request to a plain, deterministic sub-workflow that does claim, re-check, book. The slow, unpredictable part stays outside the window where races happen.

How to test for it before customers do

Fire the same request at the webhook twenty times at once (a short script or a load-testing tool does it in seconds) and count the bookings. One is the right answer. Then resend a single webhook event three times and check that nothing happens twice. Both tests take minutes and catch the failure that ordinary click-through testing never will.

Need this built?

Entech Solutions builds n8n agent workflows that hold up under real traffic: claim-first concurrency, idempotent side effects, and load tests before launch. See our n8n AI and agentic workflow design page, our guide to n8n error handling, or get in touch to review a workflow you already run.

Top comments (0)