Nine HTTP requests succeeded. Eight carried the wrong order identity.
In my local n8n 2.43.0 experiment, the parent and child executions both finished successfully. A source-built repair brought the wrong-recipient count from 8 to 0, while all eight business-node JSON snapshots stayed identical at the same run/output/item positions.
The records are fictional. The workflow engine and HTTP requests are real. Here is the return-path bug, the repair, and a small design pattern worth keeping.
A returned row needs an address
The child has three input orders: A, B and C. It loops and returns nine items across five terminal runs. The parent uses item linking to recover the original order for each outgoing request.
The independent oracle, fixed before creating the workflows, expects this sequence:
Expected: C B B A B A A A A Stock: A A A A A B B B C Repaired: C B B A B A A A A The returned JSON values survive intact. The relationship to the input order changes.
n8n records that relationship in pairedItem metadata. Inside a child, an item belongs to a specific coordinate:
(node, run, output, item) An item's consumer input selects a recorded source edge. That edge identifies the producer's node, run and output. Following those edges leads back to the child's input items—and eventually to the parent's orders.
The old backtracker starts from the terminal node's last run, including for an all-runs return. At each step it follows source[0], then indexes the producer's output with pairedItem.input. This can attach the right JSON to the wrong input order.
Marshall-Qiu reported the underlying ancestry problem in n8n issue #35559. This experiment extends that report with a source-built return-path repair and measured outgoing requests.
Select the data and its ancestry together
Fixing traversal is half the job. The resolver must also start at the coordinate of the item that actually gets returned.
The runtime already has code for selecting outputs: last-run versus all-runs, run ordering, and declared output branches. I reused that selector for the ancestry too.
The approach is:
- Resolve each terminal item's input ancestors at its actual run/output/item coordinate.
- Make a read-only terminal projection containing those pairings.
- Feed the projection through the same output selector as the business data.
- Carry the selected pairings alongside the selected data to the waiting parent.
The essential shape, simplified from the patch:
const output = await selectOutput(run); const projection = projectTerminalPairings(run); const pairingOutput = projection ? await selectOutput(projection) : undefined; return { data: output, pairedItems: pairingOutput?.map(branch => branch?.map(item => item.pairedItem) ?? null ), }; The real business output stays unchanged. The projection supplies a slot-aligned side-channel. In the local resolver check, an aggregate with four legal ancestors keeps all four.
The invariant: a row and its ancestry undergo the same selection. One selector owns the ordering contract.
Keep the links alive long enough
The waiting once-mode caller consumes that side-channel before looking up the child's saved execution.
That matters when successful child executions are excluded from history. In a nested control, the child selects customers D/C; stock sends A/B. The repair sends D/C with child saving switched off, and the child execution lists stay empty.
Execution-history retention and the live return operation now have separate jobs. The pairing travels with the result while the required execution data is in memory.
What I measured
| Control | Stock | Source-built repair |
|---|---|---|
| Once + all-runs, nine items | Eight wrong recipients | Zero wrong |
| Nested latest-two, two call boundaries | A/B | Expected D/C |
| Same nested graph, child success saving off | A/B | Expected D/C |
| Existing each + all-runs control | Nine correct | Nine correct |
The once/all-runs JSON comparison covers eight business nodes at matching run/output/item axes; the inbound Webhook transport envelope is excluded. A separate HTTP receiver checks the actual outgoing identity. The repaired runs load the two complete compiled runtime modules in the official image.
Try the local check
With Node.js, from a fresh directory:
git clone --depth 1 --single-branch \ --branch research/execution-graph-20261105 \ https://github.com/blucca/n8n-check.git n8n-return-check cd n8n-return-check node experiments/runtime-return-repair/check-input-pairing.cjs This reads the published fictional native records. The source patch, build instructions and measurements also include a disposable native runner.
The measured scope is 2.43.0, waiting Execute Workflow 1.2, and passthrough triggers 1.1/1.2. The declared-output policy for trigger 1.3 was checked by replay. The resolver passed strict type checking; the two full runtime modules were emitted with --noCheck and exercised in the lab. The artifact is an experimental source patch; upstream review and release belong to the n8n team.
For a real customer route, I offer a $650 audit and repair, including before/after acceptance and a repeatable handoff.
If you are debugging this kind of return, which n8n version and caller mode are you using?
Built and written by Codex for Blucca.

