Module 1: From Builder to System Owner

4. The execution model in n8n 2.0

Description

By the end of this lesson you'll be able to explain what happens, under the hood, when something triggers a workflow: what an execution is, how an item flows from node to node, where you see what occurred, and —most important for this guide— what exactly happens when you retry. You're going to understand why a retry re-runs nodes, which is the precise mechanism that turns an innocent retry into a duplicate charge. And you're going to learn about the changes n8n 2.0 brought, in particular the isolation of the code node with task runners, and what part of that affects how you design.

This matters because up until now we've talked about the duplicate as something that "happens," without seeing the machinery. This lesson is the machinery. Once you understand that an execution is a complete run of the workflow and that retrying means running that machinery again, the duplicate problem stops being an abstract idea and becomes something you can point at: "right here, in this retry, this node runs a second time and charges again."

Connection to the module: in lesson 3 you walked away with the criterion —is each effect safe to repeat?— but "repeat" was still an abstract word. This lesson makes it concrete by showing you the three moments when an effect repeats: a node retry, a full execution retry, and the double trigger (which is lesson 5). From here on, whenever we say "it runs twice," you're going to know exactly which machinery is running twice. Lesson 5 takes this model and adds the reason triggers themselves deliver duplicates; lesson 6 uses this model to map the failure modes.

An honesty note about versions. n8n changes fast, and the exact detail of its execution engine evolves between versions. What follows describes n8n 2.0's conceptual model and is written to stay true at the concept level even if a button name or a numeric limit changes. Where a fact is specific and verifiable, I flag it. Where I can't confirm it against the official documentation, I tell you to verify it on your own instance. This guide's rule is not to make up n8n behavior: when in doubt, I teach the concept and send you to check.

What an execution is

Let's start with the word we're going to use hundreds of times.

An execution is a complete run of a workflow, from start to finish, triggered by an event. When something triggers your workflow —a webhook receiving an order, a timer firing, a click on "execute"— n8n creates an execution: it takes the trigger's data, passes it through the first node, then the next, and so on to the end. That entire run, with all its nodes and all its data, is one execution.

Think of it as a ticket in a restaurant kitchen. Every time an order comes in —"a table ordered the special"— the kitchen opens a new ticket and carries it from station to station: first the person who cuts, then the person who cooks, then the person who plates. That specific ticket, with its slip, its route, and its result, is an execution. If three identical orders come in, there are three tickets, each with its own slip and its own route, even though the dish is the same.

Here's the first fact that this entire guide stands on, and I want you to fix it in your mind:

Every trigger creates an execution. One trigger, one execution. Two triggers, two executions.

This sounds obvious, but it has a huge consequence. If the same order triggers the webhook twice —because of a provider retry, for example— n8n doesn't see "the same order arriving again." n8n sees two triggers, and creates two independent executions. Each one starts from scratch, with no knowledge that the other exists. Each one runs order-triage's six nodes. Each one creates its record, makes its charge, sends its email. n8n has no memory, on its own, that it already processed that order: for that you'd have to give it that memory, and that's Module 4.

This is exactly why the duplicate is so easy to produce: n8n's natural unit is the trigger, not the business order. You think in terms of orders ("I already processed this order"); n8n thinks in terms of triggers ("I got triggered, I execute"). Closing that gap —teaching n8n to think in terms of orders— is a big part of this guide's work.

Think of it like a receptionist who logs every visit in a notebook. If the same person comes in, steps out to grab something from the car, and comes back in, the receptionist logs two visits —because their unit is "someone walked through the door," not "distinct people who came today"—. They're not wrong: they're doing their job, which is to record every crossing. To know it was the same person twice, they'd need to compare some piece of data —the name, an ID— against what they already logged. n8n is that receptionist: it records every door-crossing as an execution, and to recognize that two executions are "the same person," it needs you to give it the data to compare (the event_id) and the place to keep track (the ledger from Module 4). Without that, every crossing is, to n8n, a new visit.

How an item flows through the workflow

Now let's drop a level, to what happens inside an execution.

You already know from previous guides that a loose piece of data doesn't travel between nodes — a list of items does. An item is a package of data —Cumbre's order, for example— and it travels from node to node. Each node receives the items from the previous node, does its job, and delivers items to the next one.

Inside an order-triage execution, an order's journey looks like this:

Webhook          →  delivers 1 item: the order { event_id, order_id, amount, ... }
  ↓
Get customer     →  receives that item, queries the CRM, delivers the item enriched with the record
  ↓
AI Agent         →  receives the item, classifies it, delivers the item + { priority, category }
  ↓
Create CRM order →  receives the item, creates the record, delivers the item + { crm_record_id }
  ↓
Create charge    →  receives the item, charges, delivers the item + { charge_id }
  ↓
Send Email       →  receives the item, sends the email, delivers the item + { email_sent: true }

Each arrow is a handoff of items. And each node, when its turn comes, executes its action. That phrase —"executes its action"— is the one that matters: when the item reaches Create charge, that node doesn't "remember" whether it already charged before; it simply executes the charge action with the data it has. If the item arrives again —in a retry or in a second execution— the node executes the charge action again. There's nothing in the item's flow that says "careful, you already did this."

It's worth noting something about branches. If your workflow splits into several branches —with an If or a Switch— each branch processes its items separately, and only the nodes an item actually passes through get executed. A node that no item passes through doesn't run. This will be relevant in lesson 6 when we talk about partial failure: which nodes an execution managed to run before failing determines what state the system was left in.

Where you see what happened: execution data

Every execution leaves a record of what occurred, and knowing how to read it is a core system owner skill. In n8n, that record lives in the execution list.

When you open a past execution, you see:

  • The status: whether it finished successfully, failed, or is still running.
  • Which nodes ran and in what order.
  • What data went in and out of each node: the exact items, with their fields, exactly as they flowed.
  • Where it failed, if it failed: the exact node that stopped and the error message.

This is gold for the system owner, because it's where you investigate an incident. When someone tells you "a customer got charged twice," your first move is going to be opening the execution list, searching for that order's executions, and seeing —with your own eyes— that there were two executions that went through Create charge. Execution data turns "something went wrong" into "this happened, right here."

Hold on to this capability, because it's the foundation of one of this guide's promises: reproducing a duplicate bug. In Module 6 you're going to use this data not just to investigate, but to re-run an execution in a controlled way and demonstrate that your protection works. That controlled re-execution is what this guide informally calls the replay engine: the ability to take an execution that already happened and run it again to study it.

Retrying: the moment an effect repeats

We've arrived at the technical heart of the lesson. Retrying is, for the system owner, the most delicate operation of all, because it's where an effect repeats on purpose —with good intentions— and can duplicate. There are two ways to retry in n8n, and it's worth telling them apart carefully.

Node retry: "Retry On Fail"

n8n lets you configure a node so that, if its action fails, it tries again automatically before giving up. In the node's settings there's a retry-on-failure option —commonly called Retry On Fail— with two parameters:

  • How many times to retry (maximum number of attempts).
  • How long to wait between attempts (a pause in milliseconds).

As of this guide's writing, those values have caps —the maximum number of attempts and the maximum wait are bounded—, and those caps can change between versions, so verify them on your instance. What matters is the concept: when you turn on retry for a node, if the node's action fails, n8n runs that action again.

Here's the dangerous detail. Imagine you turn on Retry On Fail on Create charge. The node calls the gateway, the gateway does charge, but right before responding to you the connection drops. From n8n's point of view, the node "failed" —it didn't get a confirmation— so it retries: it calls the gateway again, which charges again. The charge happened twice, even though n8n believed the first one had failed. The retry, meant to recover from a failure, produced a duplicate, because it doesn't distinguish between "the action didn't happen" and "the action happened but I didn't find out."

Think of it like asking someone to mail an important letter and telling them "if I don't confirm it arrived, mail it again." If the letter arrives but the confirmation gets lost along the way, the person sends a second letter. The recipient gets two. The instruction was reasonable; the result is a duplicate, because "I wasn't confirmed" isn't the same as "it didn't arrive."

That's why automatic retry on a node that performs an effect is a double-edged tool: excellent for reads (retrying a CRM lookup does no harm) and dangerous for unprotected effects (retrying a charge can charge twice). Module 6 devotes a whole lesson to safe retries, and you're going to recognize the condition for a retry to be safe immediately: that the effect be idempotent.

Full execution retry

The second way is to re-run an entire execution from the execution list. When an execution failed, n8n offers to retry it, and —this is a detail worth verifying on your version— it usually gives you two options for which version of the workflow to use:

  • Retry with the original workflow: uses the version of the workflow exactly as it was when the original execution happened.
  • Retry with the current workflow: uses the most recent version you have saved.

The distinction between the two matters for debugging —if you fixed the workflow, you might want to retry with the new version— but for our purposes what's crucial is what they have in common: the retry re-runs the nodes. And here's the problem you already saw in lesson 2's timeline: if the original execution managed to charge before failing on the email, the full-workflow retry goes through the charge again and charges again.

This is the exact mechanism behind that table. It isn't magic or a bug: it's that retrying a full execution literally means running its nodes again, and nodes execute their actions without asking whether they already did so.

From here comes a practical rule that Module 6 formalizes, but that you can already sense: retrying a full execution is only safe if all the effects that would repeat are idempotent. If they aren't, a retry meant "to fix" a partial failure can make things worse. The alternative —retrying only the part that failed, without repeating what's already done— requires the system to remember what it already did, and that's Module 4's ledger.

Two executions at the same time: the race

There's a detail about the double trigger worth seeing now, because it takes apart a solution almost everyone thinks of first, and understanding it early saves you from building something that doesn't work.

When the provider resends the order two seconds apart, the two executions don't run one after the other in an orderly way: they can run almost at the same time, overlapping. Execution #4471 hasn't finished yet when #4472 already started. Both are alive at once, each moving through its own nodes.

This breaks the first idea that comes up to avoid duplicates. The natural idea is: "before charging, have the workflow check whether this order has already been charged; if it has, don't charge." Sounds perfect. But watch what happens with two overlapping executions:

Execution #4471:  checks "has ORD-2041 already been charged?"  →  no  →  charges
Execution #4472:  checks "has ORD-2041 already been charged?"  →  no  →  charges
                  (both checked BEFORE the other had a chance to charge)

Both executions ask "has it already been charged?" at almost the same time, before either one has managed to charge. Both get the same answer: "no, not yet." And both, trusting that answer, charge. "Check before acting" didn't save them, because between checking and acting there was a gap of time in which the other execution also checked.

This dangerous pattern —checking the state and then acting on that check, with a gap in between where another execution can slip in— is called "check-then-act", and it's one of the most important traps in this entire guide. It gets a whole lesson in Module 2, because the naive solution to duplicates almost always falls into it.

You don't need the solution yet. What I want you to take away from here is a warning that's going to save you time: when you get to Module 2 and want to avoid duplicates, your first instinct is going to be "I check, then I charge," and that instinct has a hole in it. The execution model you just learned —two triggers, two overlapping executions— is the reason. The real solution isn't to check beforehand, but to make the effect itself idempotent, so that it doesn't even matter if two executions attempt it at the same time. But to appreciate why that's the solution, you first had to see why the obvious one fails.

What changed in n8n 2.0

n8n 2.0 —whose 2.x line was released in late 2025— brought a revamped canvas and engine. For this guide, the most relevant change is how code runs.

In earlier versions, the code node ran closer to n8n's main process. In 2.0, code runs isolated in a separate process called a task runner. This isolation has several consequences, and the most important one for you is a tightening of what the code node can do. n8n's official documentation is explicit about a restriction this guide respects in all its examples:

From the code node, you cannot access the file system or make HTTP requests.

In other words: inside a Code node you're not going to call an API, read a file, or query an external service. That's what the dedicated nodes are for —the HTTP Request node to call APIs, the file read/write node for disk—. This has a direct consequence for our topic: every external effect in this guide —creating a record, charging, sending an email— is done by the HTTP Request node (or a specific integration node), never by the Code node. We're going to use the Code node to think and transform data —for example, to build an idempotency key from the event_id—, not to execute the effect.

A couple of details about the Code node's environment worth keeping in mind, because they bound what you can write:

  • In n8n Cloud, the Code node only has two modules available: crypto (for things like computing a hash) and moment (for dates). No installing external libraries.
  • Self-hosted, you can enable additional modules through configuration, but it's a deliberate step by the administrator, not something on by default.
  • Don't count, inside the Code node, on being able to fetch, or require anything beyond what's allowed, or access system environment variables as if nothing were restricted. n8n 2.0 specifically tightened those accesses.

You don't need to memorize this list; you're going to have it handy when you write code in the following modules. What I do want you to take away is the principle: the Code node thinks, the HTTP Request node acts. That separation isn't a whim of n8n's; it's what lets you, later on, put the idempotency protection in the right place.

Worked example: tracing a duplicate charge in the execution data

Let's do, in your head, what you'd do on a real instance: trace a duplicate using the model you just learned.

Suppose Cumbre reports that Luna Coffee got charged twice for order ORD-2041. You open the execution list and filter by that order. You find this:

Execution #4471   09:12:03   ✔ success   went through: Webhook → ... → Create charge → Send Email
Execution #4472   09:12:05   ✔ success   went through: Webhook → ... → Create charge → Send Email

What to expect and how to read it. Two executions, two seconds apart, both successful, both went through Create charge. Neither failed. Neither has an error. And yet the customer paid twice. This tells you, unambiguously, that it wasn't an internal failure: it was a double trigger. The webhook received the same order twice —09:12:03 and 09:12:05— and, true to its model, n8n created two independent executions, each one ran the charge, each one charged.

If you open the Webhook node of each execution and compare the input data, you're going to see the same event_id: "evt_8f2a91c4" in both. There's the proof: two executions, one single event. The provider retried, or someone double-clicked, and since nobody was looking at the event_id to discard the second one, both went through.

Notice what this exercise gave you: starting from "charged twice" and using only the execution model —every trigger an execution, the data of each execution— you arrived at the exact diagnosis (double trigger, same event_id) and at the shape of the solution (look at the event_id to discard the second one). That's exactly what you're going to do for real in Module 6, and it's one of this guide's promises being fulfilled.

Common mistakes

Believing n8n remembers it already processed something (conceptual). What happens: someone assumes that if the same order arrives twice, n8n will notice and won't process it again. Then the duplicate shows up. Why it happens: it's a reasonable expectation —humans remember— but n8n, by default, has no memory between executions: every trigger is a new execution starting from scratch. How to spot it: if your design depends on "n8n not reprocessing the same order" without you having built that memory, the design has a hole. How to fix it: you have to give it that memory yourself, with a durable place that remembers which orders have already been processed. That's Module 4's ledger. Until it exists, assume n8n will process every trigger as if it were new.

Turning on Retry On Fail on a node that performs an effect, with no protection (practical). What happens: someone, looking for robustness, turns on automatic retry on Create charge or on a node that creates records, and discovers that under certain failures the node charges or creates twice. Why it happens: the retry doesn't distinguish between "the action didn't happen" and "the action happened but I wasn't confirmed," so it retries in both cases. How to spot it: if you have Retry On Fail on for a node whose action creates, charges, sends, or deletes, and that effect isn't idempotent, you have a potential duplicator. How to fix it: reserve automatic retry for reads and for effects you've already made idempotent. For an unprotected charge, first make it idempotent (Module 2), then retry. The order isn't negotiable.

Retrying a full execution to fix a partial failure (practical). What happens: an execution failed at the last step —the email— and someone retries it in full so the email goes out. The retry creates the record again and charges again. Why it happens: "retrying" sounds like "finishing what was missing," but it actually re-runs every node, including the ones that already completed. How to spot it: if you retry full executions of workflows with unprotected effects, every retry is a duplicate. How to fix it: until the effects are idempotent, don't retry the full execution of a partial failure; resolve the missing part some other way. With idempotent effects (Module 2) and a ledger that remembers what was done (Module 4), retrying becomes a safe tool again.

Trying to perform an external effect from the Code node (practical). What happens: someone writes a fetch or a require of an HTTP library inside a Code node to call an API, and the node fails or behaves oddly, especially in n8n 2.0. Why it happens: the documentation is explicit that the Code node can't make HTTP requests or access the file system, and 2.0 tightened that isolation with task runners. How to spot it: if your Code node is trying to call an external service, you're using the wrong tool. How to fix it: external effects go in the HTTP Request node; the Code node is reserved for transforming data and making decisions. This separation, besides being mandatory, is what lets you put idempotency in the right place later on.

Exercises

Exercise 1 — Count the executions. For each situation, say how many order-triage executions n8n creates and why.

(a) An order triggers the webhook once. (b) The provider, not receiving a confirmation, resends the same order three times total. (c) An order triggers the webhook, the execution fails at Send Email, and you retry it in full once. (d) Three different orders arrive almost at the same time.

See solution

(a) One execution. One trigger, one execution. The normal case.

(b) Three executions. Each resend is a trigger, and each trigger creates an independent execution, even though the order is the same. All three run the six nodes; with no protection, all three charge.

(c) Two executions. The original (which failed) and the retry (which runs again). The retry re-runs the nodes the original had already completed, so it creates the record again and charges again.

(d) Three executions, one per order. There's no duplicate here —they're three distinct orders— but they run in parallel, which will be relevant in Module 5 when we talk about coordination and ordering.

Why this works: notice that in (b) and (c) there are more executions than business orders, and that excess is exactly where duplicates are born. n8n's unit is the trigger; the business's unit is the order; the mismatch between the two is the problem.

Exercise 2 — Diagnose using execution data. A customer reports receiving three identical confirmation emails for a single order. You open the execution list and find four executions for that order: three successful and one that failed at Create charge. Using only this lesson's model, what probably happened and what would you check to confirm it?

See solution

Most likely: the order reached the webhook four times (four triggers → four executions). Three of those executions completed the whole flow, including Send Email, and that's why three emails went out. The fourth failed at Create charge —maybe a gateway blip— and that's why it didn't send its email (it never reached Send Email).

To confirm it, I'd check two things. First: the event_id in the Webhook node across all four executions. If it's the same in all four, it confirms it was one order resent four times, not four different orders. Second: in the three successful executions, I'd check the Create charge node to see whether the customer was also charged three times —which would turn an annoying problem (three emails) into a serious one (three charges)—.

Why this works: starting from just "three emails" and using the model —every trigger an execution, the data of each execution— you rebuilt the full story and knew exactly which field to look at to confirm it. That's the investigative skill the system owner uses every day.

Exercise 3 — Classify the retry risk. For each order-triage node, say whether turning on Retry On Fail (automatic retry on failure) would be safe or dangerous as the workflow stands today, with no idempotency protection at all, and why.

(a) Get customer (looks up the customer record in the CRM). (b) Create CRM order (creates the order record). (c) Create charge (charges). (d) Send Email (sends the email).

See solution

(a) Get customer: safe. It's a read. Looking up the customer record two, three, or ten times always gives the same result and doesn't change anything in the CRM. Retrying a read never causes harm. This is the case where Retry On Fail shines.

(b) Create CRM order: dangerous. It's an unprotected creation effect. If the creation happened but the confirmation was lost, the retry creates a second record. Duplicate.

(c) Create charge: dangerous, and the most serious. Unprotected charging effect. A retry can charge twice, and a duplicate charge is among the least reversible, most costly kinds of harm.

(d) Send Email: dangerous, but less serious. Sending effect. A retry can send a second email. Awkward, rarely costly, but still a duplicate.

Why this works: notice the pattern, which is lesson 7's. The only node that's safe to retry is the only one that's a read. Every effect is dangerous to retry as long as it's unprotected. Retrying isn't good or bad in itself; its safety depends entirely on whether what it's retrying is a read or a protected effect.

Summary and next step

In this lesson you opened up n8n 2.0's engine. An execution is a complete run of the workflow triggered by an event, and the fact this whole guide stands on is that every trigger creates an independent execution: n8n thinks in triggers, not business orders, and that's why it doesn't remember on its own that it already processed something. Inside an execution, items flow from node to node, and each node executes its action without asking whether it already did so before. Execution data —the record of what happened in each run— is your window for investigating an incident and, later, for reproducing it.

The technical heart was the retry, which exists in two forms: a node's (Retry On Fail, with its attempt count and wait time) and a full execution's (with the option to use the original or current workflow). Both share the essential part: they re-run actions, and that's why a retry over an unprotected effect can duplicate —the node doesn't distinguish between "it didn't happen" and "it happened but I wasn't confirmed"—. And you saw the n8n 2.0 change that affects you most: the Code node runs isolated in a task runner, can't make HTTP requests or touch the file system, so external effects always go through the HTTP Request node. The Code node thinks; the HTTP Request node acts.

Before moving on you should be able to: explain why two triggers of the same order create two executions that both charge; describe why an automatic retry of a charge can duplicate even though the retry was well-intentioned; and say where external effects should live and why not in the Code node.

You already know that every trigger is an execution and that retrying re-executes. What's left is the loose question: why do triggers themselves deliver the same event twice in the first place? It isn't an accident or a bug on the provider's part. Lesson 5 shows you that almost every trigger offers a guarantee called "at least once" —never "exactly once"— and that this guarantee is the underlying reason the duplicate problem isn't a rare exception, but the rule you have to design around.

Resources

  • Executions — n8n Docs — the execution list: where you see the status, the nodes, and the data of each run. The central tool for investigating duplicates.
  • Retry an execution — n8n Docs — how to retry executions in n8n; worth confirming on this page the exact options your version offers (original vs. current workflow).
  • Code node — n8n Docs — the official docs page for the Code node, with the explicit restriction that it can't access the file system or make HTTP requests, and notes on available modules.
  • Task runners — n8n Docs — the explanation of the code isolation n8n 2.0 introduced; useful for understanding why the Code node is more restricted than before.
  • Release notes 2.x — n8n Docs — the version history for version 2; check here which version you're running against the model this lesson describes.