From a9a450b8d99fa253dc1539ad375c4c2d8df03d6e Mon Sep 17 00:00:00 2001 From: Danny Martini Date: Thu, 2 Oct 2025 16:16:53 +0200 Subject: [PATCH] fix(core): Fix paired item data retrieval in loops with IF nodes (#20309) --- ...process-process-run-execution-data.test.ts | 139 ------------------ .../src/execution-engine/workflow-execute.ts | 17 --- 2 files changed, 156 deletions(-) diff --git a/packages/core/src/execution-engine/__tests__/workflow-execute-process-process-run-execution-data.test.ts b/packages/core/src/execution-engine/__tests__/workflow-execute-process-process-run-execution-data.test.ts index 3d40f3d2e36..4081c6ba1e4 100644 --- a/packages/core/src/execution-engine/__tests__/workflow-execute-process-process-run-execution-data.test.ts +++ b/packages/core/src/execution-engine/__tests__/workflow-execute-process-process-run-execution-data.test.ts @@ -630,143 +630,4 @@ describe('processRunExecutionData', () => { expect(result.data.resultData.lastNodeExecuted).toBeUndefined(); }); }); - - describe('pairedItem sourceOverwrite handling', () => { - test('preserves sourceOverwrite from existing pairedItem object', async () => { - // ARRANGE - const node = createNodeData({ name: 'testNode', type: types.passThrough }); - const workflow = new DirectedGraph() - .addNodes(node) - .toWorkflow({ name: '', active: false, nodeTypes, settings: { executionOrder: 'v1' } }); - - // Create execution data with items that have pairedItem.sourceOverwrite - const sourceOverwriteData = { - previousNode: 'CustomPreviousNode', - previousNodeOutput: 2, - previousNodeRun: 1, - }; - - const taskDataConnection = { - main: [ - [ - { - json: { data: 'test1' }, - pairedItem: { - item: 0, - input: 0, - sourceOverwrite: sourceOverwriteData, - }, - }, - { - json: { data: 'test2' }, - pairedItem: { - item: 1, - input: 0, - // No sourceOverwrite - should be undefined - }, - }, - ], - ], - }; - - const executionData: IRunExecutionData = { - startData: { startNodes: [{ name: node.name, sourceData: null }] }, - resultData: { runData: {} }, - executionData: { - contextData: {}, - nodeExecutionStack: [{ data: taskDataConnection, node, source: null }], - metadata: {}, - waitingExecution: {}, - waitingExecutionSource: {}, - }, - }; - - const workflowExecute = new WorkflowExecute(additionalData, executionMode, executionData); - - // ACT - const result = await workflowExecute.processRunExecutionData(workflow); - - // ASSERT - const runData = result.data.resultData.runData; - expect(runData[node.name]).toHaveLength(1); - - const nodeExecutionData = runData[node.name][0].data?.main?.[0]; - expect(nodeExecutionData).toHaveLength(2); - - // First item should preserve sourceOverwrite - expect(nodeExecutionData?.[0].pairedItem).toEqual({ - item: 0, - input: undefined, // input index 0 becomes undefined - sourceOverwrite: sourceOverwriteData, - }); - - // Second item should have undefined sourceOverwrite - expect(nodeExecutionData?.[1].pairedItem).toEqual({ - item: 1, - input: undefined, - sourceOverwrite: undefined, - }); - }); - - test('handles non-object pairedItem gracefully', async () => { - // ARRANGE - const node = createNodeData({ name: 'testNode', type: types.passThrough }); - const workflow = new DirectedGraph() - .addNodes(node) - .toWorkflow({ name: '', active: false, nodeTypes, settings: { executionOrder: 'v1' } }); - - // Create execution data with items that have non-object pairedItem - const taskDataConnection = { - main: [ - [ - { - json: { data: 'test1' }, - pairedItem: [], // This should result in undefined sourceOverwrite - }, - { - json: { data: 'test2' }, - // No pairedItem at all - }, - ], - ], - }; - - const executionData: IRunExecutionData = { - startData: { startNodes: [{ name: node.name, sourceData: null }] }, - resultData: { runData: {} }, - executionData: { - contextData: {}, - nodeExecutionStack: [{ data: taskDataConnection, node, source: null }], - metadata: {}, - waitingExecution: {}, - waitingExecutionSource: {}, - }, - }; - - const workflowExecute = new WorkflowExecute(additionalData, executionMode, executionData); - - // ACT - const result = await workflowExecute.processRunExecutionData(workflow); - - // ASSERT - const runData = result.data.resultData.runData; - expect(runData[node.name]).toHaveLength(1); - - const nodeExecutionData = runData[node.name][0].data?.main?.[0]; - expect(nodeExecutionData).toHaveLength(2); - - // Both items should have undefined sourceOverwrite since pairedItem wasn't an object - expect(nodeExecutionData?.[0].pairedItem).toEqual({ - item: 0, - input: undefined, - sourceOverwrite: undefined, - }); - - expect(nodeExecutionData?.[1].pairedItem).toEqual({ - item: 1, - input: undefined, - sourceOverwrite: undefined, - }); - }); - }); }); diff --git a/packages/core/src/execution-engine/workflow-execute.ts b/packages/core/src/execution-engine/workflow-execute.ts index ad9f8e420f6..b2597ccf900 100644 --- a/packages/core/src/execution-engine/workflow-execute.ts +++ b/packages/core/src/execution-engine/workflow-execute.ts @@ -1565,23 +1565,6 @@ export class WorkflowExecute { } return input.map((item, itemIndex) => { - // Preserve any existing sourceOverwrite from the pairedItem. - // This allows nodes like SplitInBatches to override the - // source information that tracks where an item originated - // from, which is critical for maintaining correct data - // lineage when nodes need to manipulate the item tracking - // chain. - if (typeof item.pairedItem === 'object' && 'sourceOverwrite' in item.pairedItem) { - return { - ...item, - pairedItem: { - item: itemIndex, - input: inputIndex || undefined, - sourceOverwrite: item.pairedItem.sourceOverwrite, - }, - }; - } - return { ...item, pairedItem: {