This is part of my build-in-public series on running LLM workflows in n8n in production. Everything here is a failure I actually watched happen, not a hypothetical.
Two workflows wrote to the same customer record on the same night. One updated the plan from a billing webhook. The other updated the contact details from a form submission. Both executions said success. The record that came out was neither update: the plan change was there, the contact change was gone, and nothing in either log suggested anything had been lost.
The mistake was assuming that two workflows touching the same row at different times is the same as two workflows touching it in sequence. It is not. Each workflow read the record, changed its own field, and wrote the whole record back. The second write overwrote the first, not because either workflow was wrong about its own field, but because neither workflow knew the other had been there in between. A read-modify-write cycle with no protection is a race, even when the two writers never run at the same second — they only have to overlap between the read and the write.
The fix is to stop writing whole records from workflows. A workflow should send a change, not a replacement: update this field, to this value, based on this version. If the version it read is no longer current, the update must fail instead of silently overwriting. That failure is not a bug in the workflow. It is the system catching a collision that would otherwise have passed as success.
Two details make this work, and both are easy to get wrong.
First, the version check has to live in the update itself, not in a node before it. "Read the version, check it is still the same, then write" is again two steps with a gap in between, and another workflow can slip into that gap. The check must be part of the same statement that writes: update where the version matches, and report how many rows changed. Zero rows changed means someone else moved first, and the workflow must re-read, re-apply its own change on top of the new state, and try again — or hand the conflict to a human if the two changes cannot both be true.
Second, retries must not replay a stale change. A workflow that failed on the version check and simply reruns from its first node will re-read, which is fine. A workflow that retries only the write step, with the old values still in memory, will fail the same way forever, or worse, succeed after a third workflow has moved the record again and land on top of work it never saw. The retry has to start from a fresh read, every time. Stale data in a retry is how a collision that was caught once gets lost a second time.
I also keep a rule for which fields a workflow is allowed to touch. Each workflow owns its fields, and it writes only those. The billing workflow never writes contact details, even if it read them. The form workflow never writes the plan, even if the form mentioned it. Narrow writes shrink the surface where two workflows can collide, and when a collision does happen, the version check tells me exactly which two changes fought over which field, instead of leaving me with a record that is quietly half right.
The rule I keep coming back to: a workflow that writes a whole record is claiming it knows the record's full state. It does not. It knows one field, at one moment, and the honest write says so.
Free workflows from this series (MIT): https://github.com/zhp910318/n8n-ai-automation-lab
The production templates in my store: https://payhip.com/haopengailab
