diff --git a/packages/cli/src/runners/default-task-runner-disconnect-analyzer.ts b/packages/cli/src/runners/default-task-runner-disconnect-analyzer.ts index d61179372b..9db537ee95 100644 --- a/packages/cli/src/runners/default-task-runner-disconnect-analyzer.ts +++ b/packages/cli/src/runners/default-task-runner-disconnect-analyzer.ts @@ -12,6 +12,10 @@ import type { DisconnectAnalyzer, DisconnectErrorOptions } from './runner-types' */ @Service() export class DefaultTaskRunnerDisconnectAnalyzer implements DisconnectAnalyzer { + get isCloudDeployment() { + return config.get('deployment.type') === 'cloud'; + } + async toDisconnectError(opts: DisconnectErrorOptions): Promise { const { reason, heartbeatInterval } = opts; @@ -22,6 +26,9 @@ export class DefaultTaskRunnerDisconnectAnalyzer implements DisconnectAnalyzer { ); } - return new TaskRunnerDisconnectedError(opts.runnerId ?? 'Unknown runner ID'); + return new TaskRunnerDisconnectedError( + opts.runnerId ?? 'Unknown runner ID', + this.isCloudDeployment, + ); } } diff --git a/packages/cli/src/runners/errors/__tests__/task-runner-disconnected-error.test.ts b/packages/cli/src/runners/errors/__tests__/task-runner-disconnected-error.test.ts new file mode 100644 index 0000000000..a6b88462e3 --- /dev/null +++ b/packages/cli/src/runners/errors/__tests__/task-runner-disconnected-error.test.ts @@ -0,0 +1,49 @@ +import { TaskRunnerDisconnectedError } from '../task-runner-disconnected-error'; + +describe('TaskRunnerDisconnectedError', () => { + it('should have the correct default error message', () => { + const error = new TaskRunnerDisconnectedError('test-runner-id', false); + + expect(error.message).toBe('Node execution failed'); + }); + + it('should have the error level set to "error"', () => { + const error = new TaskRunnerDisconnectedError('test-runner-id', false); + + expect(error.level).toBe('error'); + }); + + it('should set the correct description for non-cloud deployments', () => { + const error = new TaskRunnerDisconnectedError('test-runner-id', false); + + expect(error.description).toContain( + 'This can happen for various reasons. Please try executing the node again.', + ); + expect(error.description).toContain( + '1. Reduce the number of items processed at a time, by batching them using a loop node', + ); + expect(error.description).toContain( + "2. Increase the memory available to the task runner with 'N8N_RUNNERS_MAX_OLD_SPACE_SIZE' environment variable", + ); + expect(error.description).not.toContain( + 'Upgrade your cloud plan to increase the available memory', + ); + }); + + it('should set the correct description for cloud deployments', () => { + const error = new TaskRunnerDisconnectedError('test-runner-id', true); + + expect(error.description).toContain( + 'This can happen for various reasons. Please try executing the node again.', + ); + expect(error.description).toContain( + '1. Reduce the number of items processed at a time, by batching them using a loop node', + ); + expect(error.description).toContain( + '2. Upgrade your cloud plan to increase the available memory', + ); + expect(error.description).not.toContain( + "Increase the memory available to the task runner with 'N8N_RUNNERS_MAX_OLD_SPACE_SIZE' environment variable", + ); + }); +}); diff --git a/packages/cli/src/runners/errors/task-runner-disconnected-error.ts b/packages/cli/src/runners/errors/task-runner-disconnected-error.ts index 6c7c49450a..3f4f468b1a 100644 --- a/packages/cli/src/runners/errors/task-runner-disconnected-error.ts +++ b/packages/cli/src/runners/errors/task-runner-disconnected-error.ts @@ -1,7 +1,34 @@ +import type { TaskRunner } from '@n8n/task-runner'; import { ApplicationError } from 'n8n-workflow'; export class TaskRunnerDisconnectedError extends ApplicationError { - constructor(runnerId: string) { - super(`Task runner (${runnerId}) disconnected`); + public description: string; + + constructor( + public readonly runnerId: TaskRunner['id'], + isCloudDeployment: boolean, + ) { + super('Node execution failed'); + + const fixSuggestions = { + reduceItems: + 'Reduce the number of items processed at a time, by batching them using a loop node', + increaseMemory: + "Increase the memory available to the task runner with 'N8N_RUNNERS_MAX_OLD_SPACE_SIZE' environment variable", + upgradePlan: 'Upgrade your cloud plan to increase the available memory', + }; + + const subtitle = + 'This can happen for various reasons. Please try executing the node again. If the problem persists, you can try the following:'; + const suggestions = isCloudDeployment + ? [fixSuggestions.reduceItems, fixSuggestions.upgradePlan] + : [fixSuggestions.reduceItems, fixSuggestions.increaseMemory]; + const suggestionsText = suggestions + .map((suggestion, index) => `${index + 1}. ${suggestion}`) + .join('
'); + + const description = `${subtitle}

${suggestionsText}`; + + this.description = description; } } diff --git a/packages/cli/src/runners/internal-task-runner-disconnect-analyzer.ts b/packages/cli/src/runners/internal-task-runner-disconnect-analyzer.ts index e27f76b628..26d8de5683 100644 --- a/packages/cli/src/runners/internal-task-runner-disconnect-analyzer.ts +++ b/packages/cli/src/runners/internal-task-runner-disconnect-analyzer.ts @@ -1,8 +1,6 @@ import { TaskRunnersConfig } from '@n8n/config'; import { Service } from 'typedi'; -import config from '@/config'; - import { DefaultTaskRunnerDisconnectAnalyzer } from './default-task-runner-disconnect-analyzer'; import { TaskRunnerOomError } from './errors/task-runner-oom-error'; import type { DisconnectErrorOptions } from './runner-types'; @@ -16,10 +14,6 @@ import { TaskRunnerProcess } from './task-runner-process'; */ @Service() export class InternalTaskRunnerDisconnectAnalyzer extends DefaultTaskRunnerDisconnectAnalyzer { - private get isCloudDeployment() { - return config.get('deployment.type') === 'cloud'; - } - private readonly exitReasonSignal: SlidingWindowSignal; constructor( diff --git a/packages/cli/src/runners/runner-types.ts b/packages/cli/src/runners/runner-types.ts index 132d688e98..ca26ed41b8 100644 --- a/packages/cli/src/runners/runner-types.ts +++ b/packages/cli/src/runners/runner-types.ts @@ -6,6 +6,8 @@ import type { TaskRunner } from './task-broker.service'; import type { AuthlessRequest } from '../requests'; export interface DisconnectAnalyzer { + isCloudDeployment: boolean; + toDisconnectError(opts: DisconnectErrorOptions): Promise; }