From 7ed93eadaee1665be7da2bcb6804f2fffc989c42 Mon Sep 17 00:00:00 2001 From: "n8n-cat-bot[bot]" <283985454+n8n-cat-bot[bot]@users.noreply.github.com> Date: Mon, 28 Sep 2026 07:18:38 +0000 Subject: [PATCH] chore: Replace repeated response-mode child scans with one predicate (#39076) Co-authored-by: n8n-cat-bot[bot] --- .../__tests__/webhook-helpers.test.ts | 17 +++++++ packages/cli/src/webhooks/webhook-helpers.ts | 44 +++++-------------- 2 files changed, 29 insertions(+), 32 deletions(-) diff --git a/packages/cli/src/webhooks/__tests__/webhook-helpers.test.ts b/packages/cli/src/webhooks/__tests__/webhook-helpers.test.ts index 8f566452a5e..ac4b7ea4993 100644 --- a/packages/cli/src/webhooks/__tests__/webhook-helpers.test.ts +++ b/packages/cli/src/webhooks/__tests__/webhook-helpers.test.ts @@ -146,6 +146,23 @@ describe('autoDetectResponseMode', () => { expect(result).toBe('responseNode'); }); + test('should return responseNode when a matching child follows a non-matching child', () => { + const workflowStartNode = mock({ + type: FORM_NODE_TYPE, + name: 'startNode', + parameters: {}, + }); + workflow.getChildNodes.mockReturnValue(['disabledChild', 'enabledChild']); + workflow.nodes.disabledChild = mock({ type: WAIT_NODE_TYPE, disabled: true }); + workflow.nodes.enabledChild = mock({ + type: WAIT_NODE_TYPE, + parameters: { resume: 'form' }, + disabled: false, + }); + const result = autoDetectResponseMode(workflowStartNode, workflow, 'POST'); + expect(result).toBe('responseNode'); + }); + test('should return formPage when start node is FORM_NODE_TYPE and method is POST and there is a following FORM_NODE_TYPE node', () => { const workflowStartNode = mock({ type: FORM_NODE_TYPE, diff --git a/packages/cli/src/webhooks/webhook-helpers.ts b/packages/cli/src/webhooks/webhook-helpers.ts index f48b096e0dd..3f9a7f85ac4 100644 --- a/packages/cli/src/webhooks/webhook-helpers.ts +++ b/packages/cli/src/webhooks/webhook-helpers.ts @@ -330,17 +330,13 @@ export function autoDetectResponseMode( workflow: Workflow, method: string, ): WebhookResponseMode | undefined { - if (workflowStartNode.type === FORM_TRIGGER_NODE_TYPE && method === 'POST') { - const connectedNodes = workflow.getChildNodes(workflowStartNode.name); + const hasChildMatching = (predicate: (node: INode) => boolean) => + workflow + .getChildNodes(workflowStartNode.name) + .some((nodeName) => predicate(workflow.nodes[nodeName])); - for (const nodeName of connectedNodes) { - const node = workflow.nodes[nodeName]; - - if (isEnabledFormPageNode(node)) { - return 'formPage'; - } - } - } + const isFormTriggerPost = workflowStartNode.type === FORM_TRIGGER_NODE_TYPE && method === 'POST'; + if (isFormTriggerPost && hasChildMatching(isEnabledFormPageNode)) return 'formPage'; const chatResponseMode = getChatResponseMode(workflowStartNode, method); if (chatResponseMode) return chatResponseMode; @@ -348,17 +344,9 @@ export function autoDetectResponseMode( // If there are form nodes connected to a current form node we're dealing with a multipage form // and we need to return the formPage response mode when a second page of the form gets submitted // to be able to show potential form errors correctly. - if (workflowStartNode.type === FORM_NODE_TYPE && method === 'POST') { - const connectedNodes = workflow.getChildNodes(workflowStartNode.name); - - for (const nodeName of connectedNodes) { - const node = workflow.nodes[nodeName]; - - if (node.type === FORM_NODE_TYPE && !node.disabled) { - return 'formPage'; - } - } - } + const isFormPost = workflowStartNode.type === FORM_NODE_TYPE && method === 'POST'; + if (isFormPost && hasChildMatching((node) => node.type === FORM_NODE_TYPE && !node.disabled)) + return 'formPage'; if (workflowStartNode.type === WAIT_NODE_TYPE && workflowStartNode.parameters.resume !== 'form') { return undefined; @@ -370,17 +358,9 @@ export function autoDetectResponseMode( ) { return 'onReceived'; } - if ([FORM_NODE_TYPE, WAIT_NODE_TYPE].includes(workflowStartNode.type) && method === 'POST') { - const connectedNodes = workflow.getChildNodes(workflowStartNode.name); - - for (const nodeName of connectedNodes) { - const node = workflow.nodes[nodeName]; - - if (isEnabledFormPageNode(node)) { - return 'responseNode'; - } - } - } + const isFormOrWaitPost = + [FORM_NODE_TYPE, WAIT_NODE_TYPE].includes(workflowStartNode.type) && method === 'POST'; + if (isFormOrWaitPost && hasChildMatching(isEnabledFormPageNode)) return 'responseNode'; return undefined; }