Static data is persisted even when the run fails — why "mark done → do the work" silently loses items

Sharing a finding that cost me a few hours, in case it saves someone else the same afternoon.

The pattern. A very common way to make a scheduled workflow idempotent is to keep a list of already-processed ids in workflow static data:


mark id as done  →  do the work (send message / call API / write row)  →  done
```

**The problem.** n8n persists static data on the `workflowExecuteAfter` hook, and that hook fires whether the execution **succeeded or failed**. I checked it in the source and then reproduced it: a workflow that marks an id done and *then* throws still has the "done" write persisted.

So when the work step fails:

- the id is marked done
- the work never happened
- the execution shows as failed, so you assume you can just re-run it
- on the re-run the id is already "done" → the item is skipped, permanently

Nothing surfaces it. The execution list looks healthy and the "processed" count even looks right — which is what makes it nasty: the failure mode is *silence*, and it triggers exactly when something downstream breaks, which is the situation you added dedup for in the first place.

**The fix is ordering, not code.** Commit after the side effect succeeds, per item:

```
check if done  →  do the work  →  only now mark it done
```

In a Code node it's just moving the write to the end:

```js
// runs only AFTER the side effect succeeded, once per item
const state = $getWorkflowStaticData('global');
state.done = state.done || [];
const key = String($json.id);
if (!state.done.includes(key)) state.done.push(key);
return $input.item;
```

Two things worth knowing when you do this:

1. **Per item, not per batch.** If you mark a whole batch done at the end, one failure re-does the rest on the next run (and re-sends those messages). Commit each item right after its own side effect.
2. **This is at-least-once, not exactly-once.** If the process dies between the side effect and the commit, that one item repeats on the next run. For most work that's the right trade — a visible duplicate beats a silent loss — but it's a choice, so make it deliberately.

If you have a scheduled workflow with a dedup list, it's worth opening it and checking the order of those two steps. Thirty seconds, and it's either fine or it's been quietly dropping things.
1 Like