From d64f2e57f7ab2f93328e56b1e46fef5e4a14e221 Mon Sep 17 00:00:00 2001 From: Jaakko Husso Date: Wed, 4 Jun 2025 19:09:13 +0300 Subject: [PATCH] feat(core): Link 'Error Trigger' nodes to the parent execution that errored (#16016) --- .../workflow-execution.service.test.ts | 112 +++++++++++++++++- .../workflows/workflow-execution.service.ts | 13 ++ 2 files changed, 124 insertions(+), 1 deletion(-) diff --git a/packages/cli/src/workflows/__tests__/workflow-execution.service.test.ts b/packages/cli/src/workflows/__tests__/workflow-execution.service.test.ts index 2068522cd1..ae41e1b3a0 100644 --- a/packages/cli/src/workflows/__tests__/workflow-execution.service.test.ts +++ b/packages/cli/src/workflows/__tests__/workflow-execution.service.test.ts @@ -1,4 +1,5 @@ -import type { User } from '@n8n/db'; +import type { GlobalConfig } from '@n8n/config'; +import type { Project, User, WorkflowEntity, WorkflowRepository } from '@n8n/db'; import { mock } from 'jest-mock-extended'; import { NodeConnectionTypes, @@ -7,8 +8,10 @@ import { type INodeType, type IWorkflowBase, type IWorkflowExecuteAdditionalData, + type ExecutionError, } from 'n8n-workflow'; +import type { IWorkflowErrorData } from '@/interfaces'; import type { NodeTypes } from '@/node-types'; import * as WorkflowExecuteAdditionalData from '@/workflow-execute-additional-data'; import type { WorkflowRunner } from '@/workflow-runner'; @@ -490,6 +493,113 @@ describe('WorkflowExecutionService', () => { }); }); }); + + describe('executeErrorWorkflow()', () => { + test('should call `WorkflowRunner.run()` with correct parameters', async () => { + const workflowErrorData: IWorkflowErrorData = { + workflow: { id: 'workflow-id', name: 'Test Workflow' }, + execution: { + id: 'execution-id', + mode: 'manual', + error: new Error('Test error') as ExecutionError, + lastNodeExecuted: 'Node with error', + }, + }; + + const workflowRunnerMock = mock(); + workflowRunnerMock.run.mockResolvedValue('fake-execution-id'); + + const errorTriggerType = 'n8n-nodes-base.errorTrigger'; + const globalConfig = mock({ + nodes: { + errorTriggerType, + }, + }); + + const errorTriggerNode: INode = { + id: 'error-trigger-node-id', + name: 'Error Trigger', + type: errorTriggerType, + typeVersion: 1, + position: [0, 0], + parameters: {}, + }; + + const errorWorkflow = mock({ + id: 'error-workflow-id', + name: 'Error Workflow', + active: false, + isArchived: false, + pinData: {}, + nodes: [errorTriggerNode], + connections: {}, + createdAt: new Date(), + updatedAt: new Date(), + }); + + const workflowRepositoryMock = mock(); + workflowRepositoryMock.findOneBy.mockResolvedValue(errorWorkflow); + + const service = new WorkflowExecutionService( + mock(), + mock(), + mock(), + workflowRepositoryMock, + nodeTypes, + mock(), + workflowRunnerMock, + globalConfig, + mock(), + mock(), + ); + + await service.executeErrorWorkflow( + 'error-workflow-id', + workflowErrorData, + mock({ id: 'project-id' }), + ); + + expect(workflowRunnerMock.run).toHaveBeenCalledTimes(1); + expect(workflowRunnerMock.run).toHaveBeenCalledWith({ + executionMode: 'error', + executionData: { + executionData: { + contextData: {}, + metadata: {}, + nodeExecutionStack: [ + { + node: errorTriggerNode, + data: { + main: [ + [ + { + json: workflowErrorData, + }, + ], + ], + }, + source: null, + metadata: { + parentExecution: { + executionId: 'execution-id', + workflowId: 'workflow-id', + }, + }, + }, + ], + waitingExecution: {}, + waitingExecutionSource: {}, + }, + resultData: { + runData: {}, + }, + startData: {}, + }, + workflowData: errorWorkflow, + projectId: 'project-id', + }); + }); + }); }); function createMainConnection(targetNode: string, sourceNode: string): IConnections { diff --git a/packages/cli/src/workflows/workflow-execution.service.ts b/packages/cli/src/workflows/workflow-execution.service.ts index 59a34665ef..139745c913 100644 --- a/packages/cli/src/workflows/workflow-execution.service.ts +++ b/packages/cli/src/workflows/workflow-execution.service.ts @@ -320,6 +320,14 @@ export class WorkflowExecutionService { return; } + const parentExecution = + workflowErrorData.execution?.id && workflowErrorData.workflow?.id + ? { + executionId: workflowErrorData.execution.id, + workflowId: workflowErrorData.workflow.id, + } + : undefined; + // Can execute without webhook so go on // Initialize the data of the webhook node const nodeExecutionStack: IExecuteData[] = []; @@ -335,6 +343,11 @@ export class WorkflowExecutionService { ], }, source: null, + ...(parentExecution && { + metadata: { + parentExecution, + }, + }), }); const runExecutionData: IRunExecutionData = {