From 87afcd8db4dfc313301416bbff8bff865e72af62 Mon Sep 17 00:00:00 2001 From: Arvin A <51036481+DeveloperTheExplorer@users.noreply.github.com> Date: Mon, 23 Mar 2026 10:56:05 +0100 Subject: [PATCH] fix(core): Force full execution data fetching for evaluation test runs (#27335) --- .vscode/launch.json | 15 ++++++ .../cli/src/__tests__/workflow-runner.test.ts | 51 +++++++++++++++++++ .../__tests__/test-runner.service.ee.test.ts | 4 ++ .../test-runner/test-runner.service.ee.ts | 2 + packages/cli/src/workflow-runner.ts | 12 ++++- packages/workflow/src/interfaces.ts | 5 ++ 6 files changed, 87 insertions(+), 2 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index bd306649f2f..b703f0e36b6 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -32,6 +32,21 @@ "outputCapture": "std", "killBehavior": "forceful" }, + { + "name": "Launch n8n CLI worker dev with debug", + "runtimeExecutable": "pnpm", + "cwd": "${workspaceFolder}/packages/cli", + "runtimeArgs": ["run", "dev:worker", "--", "--inspect-brk"], + "console": "integratedTerminal", + "restart": true, + "autoAttachChildProcesses": true, + "request": "launch", + "skipFiles": ["/**"], + "type": "node", + "envFile": "${workspaceFolder}/.env", + "outputCapture": "std", + "killBehavior": "polite" + }, { "name": "Launch n8n CLI dev with debug", "runtimeExecutable": "pnpm", diff --git a/packages/cli/src/__tests__/workflow-runner.test.ts b/packages/cli/src/__tests__/workflow-runner.test.ts index b97a258869b..1a38c7e941d 100644 --- a/packages/cli/src/__tests__/workflow-runner.test.ts +++ b/packages/cli/src/__tests__/workflow-runner.test.ts @@ -572,6 +572,57 @@ describe('workflow timeout with startedAt', () => { }); }); +describe('needsFullExecutionData', () => { + const originalEnv = process.env.N8N_MINIMIZE_EXECUTION_DATA_FETCHING; + + afterEach(() => { + if (originalEnv === undefined) { + delete process.env.N8N_MINIMIZE_EXECUTION_DATA_FETCHING; + } else { + process.env.N8N_MINIMIZE_EXECUTION_DATA_FETCHING = originalEnv; + } + }); + + it('should return true when forceFullExecutionData is true even with N8N_MINIMIZE_EXECUTION_DATA_FETCHING set', () => { + process.env.N8N_MINIMIZE_EXECUTION_DATA_FETCHING = 'true'; + + // @ts-expect-error Private method + const result = runner.needsFullExecutionData('evaluation', 'exec-id', true); + + expect(result).toBe(true); + }); + + it('should return true when env var is not set and forceFullExecutionData is undefined', () => { + delete process.env.N8N_MINIMIZE_EXECUTION_DATA_FETCHING; + + // @ts-expect-error Private method + const result = runner.needsFullExecutionData('webhook', 'exec-id', undefined); + + expect(result).toBe(true); + }); + + it('should return false when env var is set, forceFullExecutionData is undefined, and mode is not integrated', () => { + process.env.N8N_MINIMIZE_EXECUTION_DATA_FETCHING = 'true'; + + const activeExecutions = Container.get(ActiveExecutions); + jest.spyOn(activeExecutions, 'getResponseMode').mockReturnValue('responseNode'); + + // @ts-expect-error Private method + const result = runner.needsFullExecutionData('webhook', 'exec-id', undefined); + + expect(result).toBe(false); + }); + + it('should return true when env var is set and mode is integrated', () => { + process.env.N8N_MINIMIZE_EXECUTION_DATA_FETCHING = 'true'; + + // @ts-expect-error Private method + const result = runner.needsFullExecutionData('integrated', 'exec-id', undefined); + + expect(result).toBe(true); + }); +}); + describe('streaming functionality', () => { it('should setup sendChunk handler when streaming is enabled and execution mode is not manual', async () => { // ARRANGE diff --git a/packages/cli/src/evaluation.ee/test-runner/__tests__/test-runner.service.ee.test.ts b/packages/cli/src/evaluation.ee/test-runner/__tests__/test-runner.service.ee.test.ts index de1771f5c14..5e72e34679f 100644 --- a/packages/cli/src/evaluation.ee/test-runner/__tests__/test-runner.service.ee.test.ts +++ b/packages/cli/src/evaluation.ee/test-runner/__tests__/test-runner.service.ee.test.ts @@ -489,6 +489,7 @@ describe('TestRunnerService', () => { resource: 'dataset', operation: 'getRows', }); + expect(runCallArg).toHaveProperty('forceFullExecutionData', true); }); test('should call workflowRunner.run with correct data in queue execution mode and manual offload', async () => { @@ -575,6 +576,7 @@ describe('TestRunnerService', () => { resource: 'dataset', operation: 'getRows', }); + expect(runCallArg).toHaveProperty('forceFullExecutionData', true); // after reset delete process.env.OFFLOAD_MANUAL_EXECUTIONS_TO_WORKERS; @@ -742,6 +744,7 @@ describe('TestRunnerService', () => { }, }, userId: metadata.userId, + forceFullExecutionData: true, triggerToStartFrom: { name: triggerNodeName, }, @@ -869,6 +872,7 @@ describe('TestRunnerService', () => { expect(runCallArg).toEqual( expect.objectContaining({ executionMode: 'evaluation', + forceFullExecutionData: true, pinData: { [triggerNodeName]: [testCase], }, diff --git a/packages/cli/src/evaluation.ee/test-runner/test-runner.service.ee.ts b/packages/cli/src/evaluation.ee/test-runner/test-runner.service.ee.ts index 4aa9cc264fd..17802cb2b80 100644 --- a/packages/cli/src/evaluation.ee/test-runner/test-runner.service.ee.ts +++ b/packages/cli/src/evaluation.ee/test-runner/test-runner.service.ee.ts @@ -250,6 +250,7 @@ export class TestRunnerService { const data: IWorkflowExecutionDataProcess = { executionMode: 'evaluation', pinData, + forceFullExecutionData: true, workflowData: { ...workflow, settings: { @@ -336,6 +337,7 @@ export class TestRunnerService { destinationNode: { nodeName: triggerNode.name, mode: 'inclusive' }, executionMode: 'manual', runData: {}, + forceFullExecutionData: true, workflowData: { ...workflow, settings: { diff --git a/packages/cli/src/workflow-runner.ts b/packages/cli/src/workflow-runner.ts index ad4b08a126d..b029ab94368 100644 --- a/packages/cli/src/workflow-runner.ts +++ b/packages/cli/src/workflow-runner.ts @@ -489,7 +489,10 @@ export class WorkflowRunner { let runData: IRun; - if (!jobResult || this.needsFullExecutionData(data.executionMode, executionId)) { + if ( + !jobResult || + this.needsFullExecutionData(data.executionMode, executionId, data.forceFullExecutionData) + ) { const fullExecutionData = await this.executionRepository.findSingleExecution( executionId, { @@ -561,7 +564,12 @@ export class WorkflowRunner { * In all other cases we can skip the DB fetch and use the lightweight * result summary sent by the worker via the job progress message. */ - private needsFullExecutionData(executionMode: WorkflowExecuteMode, executionId: string): boolean { + private needsFullExecutionData( + executionMode: WorkflowExecuteMode, + executionId: string, + forceFullExecutionData?: boolean, + ): boolean { + if (forceFullExecutionData) return true; if (!process.env.N8N_MINIMIZE_EXECUTION_DATA_FETCHING) return true; return ( diff --git a/packages/workflow/src/interfaces.ts b/packages/workflow/src/interfaces.ts index 27fe4ff8f0c..d136d1dd707 100644 --- a/packages/workflow/src/interfaces.ts +++ b/packages/workflow/src/interfaces.ts @@ -2880,6 +2880,11 @@ export interface IWorkflowExecutionDataProcess { destinationNode?: IDestinationNode; restartExecutionId?: string; executionMode: WorkflowExecuteMode; + /** + * When true, forces the execution data to be present in the run data + * ignores N8N_MINIMIZE_EXECUTION_DATA_FETCHING environment variable if set + */ + forceFullExecutionData?: boolean; /** * The data that is sent in the body of the webhook that started this * execution.