n8n - ✅(Solved) Fix Unexpected behaviour of .reverse() on an array (mutation of the previous result, and next result set to null) [1 pull requests, 4 comments, 4 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
n8n-io/n8n#30708Fetched 2026-05-20 03:59:14
View on GitHub
Comments
4
Participants
4
Timeline
16
Reactions
0
Author
Timeline (top)
commented ×4labeled ×4mentioned ×3subscribed ×3

Error Message

  • error: all

Fix Action

Fixed

PR fix notes

PR #30720: fix(core): Prevent in-place array mutation in expression evaluations

Description (problem / solution / changelog)

Summary

This PR resolves an issue where standard n8n expression evaluations could unexpectedly mutate reference arrays in-place (e.g. via .reverse(), .sort(), or .splice()).

Root Cause

Evaluating mutating array methods inside expressions mutated the shared input data in the workflow runner memory. Since this state persists across sibling fields and subsequent parameters within the same node execution, the side-effects leaked. As a result, subsequent evaluations retrieved altered/reversed data or occasionally resolved to null.

Resolution

  • Standardized the sandboxing behavior in the core WorkflowDataProxy constructor by always wrapping runExecutionData and connectionInputData inside the existing copy-on-write augmentObject and augmentArray proxies (which were previously only enabled for scripting nodes).
  • Exported the recursive augment helper from packages/workflow/src/augment-object.ts for clean individual property proxy wrapping.
  • Intercepted all mutation attempts (set/deleteProperty traps) so that methods like .reverse() or .sort() operate cleanly on cloned array slices in local proxy cache without corrupting the underlying global state.

How to test

  1. Create a simple workflow with a Set node containing two fields:
    • reversed: {{ $json.array.reverse() }}
    • original: {{ $json.array }}
  2. Submit an array (e.g. [1, 2, 3, "four"]) to the node.
  3. Observe that both values evaluate correctly: reversed evaluates to ["four", 3, 2, 1] and original evaluates to [1, 2, 3, "four"] without order leakage or mutating each other, regardless of evaluation order.

Related Linear tickets, Github issues, and Community forum posts

Fixes #30708

Review / Merge checklist

  • I have seen this code, I have run this code, and I take responsibility for this code.
  • PR title and summary are descriptive. (conventions)
  • Docs updated or follow-up ticket created.
  • Tests included.
  • PR Labeled with Backport to Beta, Backport to Stable, or Backport to v1 (if the PR is an urgent fix that needs to be backported)

Changed files

  • packages/workflow/src/augment-object.ts (modified, +1/-1)
  • packages/workflow/src/workflow-data-proxy.ts (modified, +3/-9)
  • packages/workflow/test/workflow-data-proxy.test.ts (modified, +226/-0)

Code Example

{
  "nodes": [
    {
      "parameters": {},
      "type": "n8n-nodes-base.manualTrigger",
      "typeVersion": 1,
      "position": [
        0,
        0
      ],
      "id": "6de26992-3164-45d4-b105-d1d7bcab454d",
      "name": "When clicking ‘Execute workflow’"
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "a4647ff5-30b9-4969-bb6e-affabeb14fdf",
              "name": "array",
              "value": "[1, 2, 3, \"four\"]",
              "type": "array"
            }
          ]
        },
        "options": {}
      },
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        208,
        0
      ],
      "id": "30a6c738-7f64-4519-8063-ad9a741bdb43",
      "name": "Set initial array"
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "a3f714ed-56d3-4cab-81e3-e47566a02eec",
              "name": "dataJson",
              "value": "={{ $json.array }}",
              "type": "array"
            },
            {
              "id": "a0f2d256-b413-4e30-ab9f-4e2a723b4a00",
              "name": "dataJsonReverse",
              "value": "={{ $json.array.reverse() }}",
              "type": "array"
            },
            {
              "id": "67fe44e8-d6c2-4cb2-8141-ef548c74b741",
              "name": "dataJsonFindLast",
              "value": "={{ $json.array.findLast(a => a) }}",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        416,
        0
      ],
      "id": "8c9669a4-7810-4161-9d34-6244f8daf681",
      "name": "Edit Fields"
    }
  ],
  "connections": {
    "When clicking ‘Execute workflow’": {
      "main": [
        [
          {
            "node": "Set initial array",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Set initial array": {
      "main": [
        [
          {
            "node": "Edit Fields",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "pinData": {},
  "meta": {
    "instanceId": "fed5dc387fbe910582453b6033258970a80b35ec85ec315717e93873e0916584"
  }
}
RAW_BUFFERClick to expand / collapse

Bug Description

Initial discovery

I noticed this bug on a workflow that used to work. I take an array of messages from an email thread, and try to find the last one that fits a given condition. I used to do it with myArray.reverse().find( /* condition */) and it worked.

After an update to 2.20.9 this weekend we noticed this workflow had stopped working.

Clean reproduction

I setup a minimal reproduction for this bug, and it appears the bug is even deeper than expected. See the screenshot below :

<img width="1825" height="716" alt="Image" src="https://github.com/user-attachments/assets/afb63423-629c-4715-b840-c424d042c2da" />

As can be seen from the screenshot :

  • The .reverse() unexpectedly mutated the result from the previous node in the UI
  • $json.array.reverse() returns the correct result in the UI (reversed array) but null in the current node result
  • As a result, findLast returns the last item from the reversed array in the UI, and the last item from the regular array in the current node result

Additionally i noticed that shuffling the order of the items in the Set node (such as swapping the positions of dataJsonReverse and dataJson) also unreverses some results and reverses others.

To Reproduce

  • Create a Set node that assigns an array to a variable
  • Create a subsequent Set node that returns :
    • the array from the previous node (untouched)
    • the array from the previous node (reversed)

The JSON for my reproduction case :

{
  "nodes": [
    {
      "parameters": {},
      "type": "n8n-nodes-base.manualTrigger",
      "typeVersion": 1,
      "position": [
        0,
        0
      ],
      "id": "6de26992-3164-45d4-b105-d1d7bcab454d",
      "name": "When clicking ‘Execute workflow’"
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "a4647ff5-30b9-4969-bb6e-affabeb14fdf",
              "name": "array",
              "value": "[1, 2, 3, \"four\"]",
              "type": "array"
            }
          ]
        },
        "options": {}
      },
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        208,
        0
      ],
      "id": "30a6c738-7f64-4519-8063-ad9a741bdb43",
      "name": "Set initial array"
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "a3f714ed-56d3-4cab-81e3-e47566a02eec",
              "name": "dataJson",
              "value": "={{ $json.array }}",
              "type": "array"
            },
            {
              "id": "a0f2d256-b413-4e30-ab9f-4e2a723b4a00",
              "name": "dataJsonReverse",
              "value": "={{ $json.array.reverse() }}",
              "type": "array"
            },
            {
              "id": "67fe44e8-d6c2-4cb2-8141-ef548c74b741",
              "name": "dataJsonFindLast",
              "value": "={{ $json.array.findLast(a => a) }}",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        416,
        0
      ],
      "id": "8c9669a4-7810-4161-9d34-6244f8daf681",
      "name": "Edit Fields"
    }
  ],
  "connections": {
    "When clicking ‘Execute workflow’": {
      "main": [
        [
          {
            "node": "Set initial array",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Set initial array": {
      "main": [
        [
          {
            "node": "Edit Fields",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "pinData": {},
  "meta": {
    "instanceId": "fed5dc387fbe910582453b6033258970a80b35ec85ec315717e93873e0916584"
  }
}

Expected behavior

The reversed array should be returned correctly rather than returning null. Also the previous result should not be mutated.

Debug Info

Debug info

core

  • n8nVersion: 2.20.9
  • platform: docker (cloud)
  • nodeJsVersion: 24.14.1
  • nodeEnv: production
  • database: sqlite
  • executionMode: regular
  • concurrency: 20
  • license: enterprise (sandbox)
  • consumerId: 00000000-0000-0000-0000-000000000000

storage

  • success: all
  • error: all
  • progress: false
  • manual: true
  • binaryMode: filesystem

pruning

  • enabled: true
  • maxAge: 720 hours
  • maxCount: 25000 executions

client

  • userAgent: mozilla/5.0 (x11; linux x86_64) applewebkit/537.36 (khtml, like gecko) chrome/148.0.0.0 safari/537.36
  • isTouchDevice: false

cluster

  • instanceCount: 1
  • versions: 2.20.9
  • instances:
    • instanceKey: 71449d6f-3e60-4d38-bc0f-7196563c5000, hostId: main-felicity-dating-n8n-59d8d8b545-mc6j6, instanceType: main, instanceRole: leader, version: 2.20.9
  • checks:
    • check: hostid-clash, status: succeeded, warnings: -
    • check: lifecycle, status: succeeded, warnings: -
    • check: split-brain, status: succeeded, warnings: -
    • check: version-mismatch, status: succeeded, warnings: -

Generated at: 2026-05-19T10:36:48.133Z

Operating System

N8N Cloud instance

n8n Version

2.20.9

Node.js Version

24.14.1

Database

SQLite (default)

Execution mode

main (default)

Hosting

n8n cloud

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

FAQ

Expected behavior

The reversed array should be returned correctly rather than returning null. Also the previous result should not be mutated.

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING

n8n - ✅(Solved) Fix Unexpected behaviour of .reverse() on an array (mutation of the previous result, and next result set to null) [1 pull requests, 4 comments, 4 participants]