Skip to content

Expressions silently resolve to null when Execute Workflow returns items with out-of-range pairedItem input index #35559

Description

@Marshall-Qiu

Bug Description

When an Execute Workflow node returns items from a sub-workflow, the sub-workflow's internal pairedItem metadata can pass through to the parent unchanged. If the sub-workflow's final node was a multi-input node (e.g. Merge) and the item came through input index ≥ 1, the parent receives an item with pairedItem: { item: 0, input: 1 } — but the Execute Workflow node only has one input.

Any downstream expression that uses paired-item resolution across that node (e.g. $('Webhook').item.json.field) then silently resolves to null. No expression error, no failed node, no log line — the node runs "successfully" with an empty value, and the failure surfaces much later (in our case as a confusing 400 from an external API that received a parameter with no value).

The behavior is intermittent in real workflows because it depends on which input of the sub-workflow's final Merge produced the winning item on that particular run — which makes it very hard to debug from the outside.

Three separate issues stack up to produce the silent null (all verified on current master, 5c20436):

  1. Sub-workflow pairedItem passthroughpackages/nodes-base/nodes/ExecuteWorkflow/ExecuteWorkflow/ExecuteWorkflow.node.ts (~L462–474): when getExecutionDataById returns no run data (e.g. sub-workflow has "save successful executions" disabled) or findPairedItemThroughWorkflowData returns undefined, the item keeps its sub-internal pairedItem verbatim, exporting an input index that is out of range in the parent context.

  2. Paired-item resolver throws a raw TypeError instead of an ExpressionErrorpackages/workflow/src/workflow-data-proxy.ts, getPairedItem: candidates whose input exceeds sourceArray.length are dropped via return [] inside the flatMap, leaving results empty. Then:

    if (results.every((result) => !result.ok)) {
      throw results[0].error;   // results is [] → results[0] is undefined
    }

    [].every(...) is vacuously true, so this line throws TypeError: Cannot read properties of undefined (reading 'error') instead of one of the intended typed errors (verified by wrapping the expression in a try/catch inside the expression itself).

  3. The expression engine swallows unknown errorspackages/workflow/src/expression.ts, renderExpression: the catch block rethrows only ExpressionError/ExpressionExtensionError/SyntaxError (plus a frontend-only TypeError case). A backend TypeError falls through and the function does return null, so the expression "succeeds" with null and the node keeps executing with an empty parameter value.

Any one of these being fixed would turn the silent wrong value into a visible, diagnosable error.

To Reproduce

Minimal setup — two workflows.

Sub-workflow (note: last node is a Merge fed only via input 2 / index 1; "save successful executions" set to Do not save so the pairedItem remap in Execute Workflow has no run data to work with):

{
  "name": "repro-sub",
  "nodes": [
    { "id": "n1", "name": "Start", "type": "n8n-nodes-base.executeWorkflowTrigger", "typeVersion": 1.1, "position": [0, 0], "parameters": { "inputSource": "passthrough" } },
    { "id": "n2", "name": "Merge", "type": "n8n-nodes-base.merge", "typeVersion": 3.2, "position": [220, 0], "parameters": {} }
  ],
  "connections": { "Start": { "main": [[ { "node": "Merge", "type": "main", "index": 1 } ]] } },
  "settings": { "executionOrder": "v1", "saveDataSuccessExecution": "none" }
}

Parent workflow (a Set node named Webhook stands in for a real webhook; the consumer node uses the paired-item expression):

{
  "name": "repro-parent",
  "nodes": [
    { "id": "p1", "name": "When clicking Test", "type": "n8n-nodes-base.manualTrigger", "typeVersion": 1, "position": [0, 0], "parameters": {} },
    { "id": "p2", "name": "Webhook", "type": "n8n-nodes-base.set", "typeVersion": 3.4, "position": [200, 0], "parameters": { "assignments": { "assignments": [ { "id": "a1", "name": "webhookUrl", "type": "string", "value": "https://example.com/webhook/abc" } ] }, "options": {} } },
    { "id": "p3", "name": "CallSub", "type": "n8n-nodes-base.executeWorkflow", "typeVersion": 1.2, "position": [420, 0], "parameters": { "source": "database", "workflowId": { "__rl": true, "value": "<repro-sub id>", "mode": "id" }, "options": {} } },
    { "id": "p4", "name": "Consumer", "type": "n8n-nodes-base.set", "typeVersion": 3.4, "position": [640, 0], "parameters": { "assignments": { "assignments": [ { "id": "b1", "name": "host", "type": "string", "value": "={{ $('Webhook').item.json.webhookUrl.includes('staging') ? 'https://staging.example.com' : 'https://prod.example.com' }}" } ] }, "options": {} } }
  ],
  "connections": {
    "When clicking Test": { "main": [[ { "node": "Webhook", "type": "main", "index": 0 } ]] },
    "Webhook": { "main": [[ { "node": "CallSub", "type": "main", "index": 0 } ]] },
    "CallSub": { "main": [[ { "node": "Consumer", "type": "main", "index": 0 } ]] }
  },
  "settings": { "executionOrder": "v1" }
}
  1. Import both workflows, point CallSub at the sub-workflow.
  2. Execute the parent.
  3. Inspect the output:
    • CallSub output item: pairedItem: { "item": 0, "input": 1 }
    • Consumer output: { "host": null }execution status: success, no error anywhere.

Control: change the sub-workflow's single connection from Merge input 1 to input 0 and run again — Consumer now outputs { "host": "https://prod.example.com" }. The one-line change toggles the bug.

Expected behavior

Either the paired-item chain resolves correctly (ideally Execute Workflow remaps or strips sub-internal pairedItem so parent-side metadata is always valid), or — at minimum — the expression fails loudly with a proper ExpressionError ("no path back to node …"), failing the node so the wrong value never propagates. An expression should never silently produce null because of internal metadata corruption.

Environment

  • n8n Version: reproduced on master @ 5c20436 (2.30.0 dev); first observed on a self-hosted 1.x deployment in queue mode
  • Node.js Version: 24.7.0
  • Database: SQLite (repro), PostgreSQL (original occurrence)
  • Execution mode: regular (repro), queue (original occurrence)
  • Operating System: macOS 15 (repro), Linux/Kubernetes (original occurrence)

Metadata

Metadata

Assignees

No one assigned

    Labels

    Needs FeedbackWaiting for further input or clarification.status:in-linearIssue or PR is now in Linearstatus:team-assignedA team has been assigned the issue or PRteam:catsIssue is with the Cats team

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions