diff --git a/packages/workflow/src/workflow.ts b/packages/workflow/src/workflow.ts index 12b1834fb7..4ff469cc86 100644 --- a/packages/workflow/src/workflow.ts +++ b/packages/workflow/src/workflow.ts @@ -943,6 +943,12 @@ export class Workflow { hasPath(fromNodeName: string, toNodeName: string, maxDepth = 50): boolean { if (fromNodeName === toNodeName) return true; + // Special case: If the source node has pinned data, consider it as having a valid path + // This is important for single node execution scenarios where pinned data creates virtual paths + if (this.getPinDataOfNode(fromNodeName)) { + return true; + } + // Get connection types that actually exist in this workflow // We need both source and destination connection types for bidirectional search const connectionTypes = new Set(); diff --git a/packages/workflow/test/workflow.test.ts b/packages/workflow/test/workflow.test.ts index 02cf9873b1..d79a8c1dfc 100644 --- a/packages/workflow/test/workflow.test.ts +++ b/packages/workflow/test/workflow.test.ts @@ -11,6 +11,7 @@ import type { INode, INodeExecutionData, INodeParameters, + IPinData, IRunExecutionData, NodeParameterValueType, } from '../src/interfaces'; @@ -3583,5 +3584,56 @@ describe('Workflow', () => { expect(workflow.hasPath('Node1', 'Node2')).toBe(false); expect(workflow.hasPath('Node2', 'Node1')).toBe(false); }); + + it('should return true when source node has pinned data (virtual path)', () => { + const nodes: INode[] = [ + { + id: '1', + name: 'Trigger', + type: 'n8n-nodes-base.executeWorkflowTrigger', + typeVersion: 1, + position: [0, 0], + parameters: {}, + }, + { + id: '2', + name: 'EditFields', + type: 'n8n-nodes-base.set', + typeVersion: 1, + position: [200, 0], + parameters: {}, + }, + ]; + + const connections: IConnections = { + Trigger: { + main: [[{ node: 'EditFields', type: 'main', index: 0 }]], + }, + }; + + const pinData: IPinData = { + Trigger: [ + { + json: { + name: 'Test item', + value: 123, + }, + }, + ], + }; + + const workflow = new Workflow({ + nodes, + connections, + active: false, + nodeTypes, + pinData, + }); + + // Should return true because Trigger has pinned data, creating a virtual path + expect(workflow.hasPath('Trigger', 'EditFields')).toBe(true); + // Should also work for self-reference with pinned data + expect(workflow.hasPath('Trigger', 'Trigger')).toBe(true); + }); }); });