From b7071c91c7ef959e1cc3d703495e4fee9fcc9744 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Braulio=20Gonz=C3=A1lez=20Valido?= Date: Mon, 5 Oct 2026 13:57:14 +0000 Subject: [PATCH] fix(ai-builder): Treat a blank folder path as no folder in the build tool (#40116) Co-authored-by: Claude Opus 5.5 (1M context) --- .../__tests__/build-workflow.tool.test.ts | 34 +++++++++++++++++++ .../tools/workflows/build-workflow.tool.ts | 5 ++- 2 files changed, 38 insertions(+), 1 deletion(-) diff --git a/packages/@n8n/instance-ai/src/tools/workflows/__tests__/build-workflow.tool.test.ts b/packages/@n8n/instance-ai/src/tools/workflows/__tests__/build-workflow.tool.test.ts index 1988afa9d7b..0ce4978587a 100644 --- a/packages/@n8n/instance-ai/src/tools/workflows/__tests__/build-workflow.tool.test.ts +++ b/packages/@n8n/instance-ai/src/tools/workflows/__tests__/build-workflow.tool.test.ts @@ -475,6 +475,40 @@ describe('createBuildWorkflowTool', () => { expect(context.workflowService.updateFromWorkflowJSON).not.toHaveBeenCalled(); expect(result.errors?.join(' ')).toContain('move-workflow-to-folder'); }); + + it.each(['', ' ', '/'])( + 'creates at the project root when folderPath is %j', + async (folderPath) => { + const { context, filePath } = makeContext({ + overrides: { folderExplorationEnabled: true }, + }); + + const result = await executeTool(createBuildWorkflowTool(context), { + filePath, + name: 'Root workflow', + folderPath, + }); + + expect(result.success).toBe(true); + expect(context.workflowService.createFromWorkflowJSON).toHaveBeenCalledWith( + expect.objectContaining({ name: 'Root workflow' }), + { markAsAiTemporary: true }, + ); + }, + ); + + it('updates an existing workflow when folderPath is blank', async () => { + const { context, filePath } = makeContext({ overrides: { folderExplorationEnabled: true } }); + + const result = await executeTool(createBuildWorkflowTool(context), { + filePath, + workflowId: 'wf-1', + folderPath: '', + }); + + expect(result.success).toBe(true); + expect(context.workflowService.updateFromWorkflowJSON).toHaveBeenCalled(); + }); }); it('builds a new workflow from a workspace source file', async () => { diff --git a/packages/@n8n/instance-ai/src/tools/workflows/build-workflow.tool.ts b/packages/@n8n/instance-ai/src/tools/workflows/build-workflow.tool.ts index d72d9123389..5cd3ca41b07 100644 --- a/packages/@n8n/instance-ai/src/tools/workflows/build-workflow.tool.ts +++ b/packages/@n8n/instance-ai/src/tools/workflows/build-workflow.tool.ts @@ -775,8 +775,11 @@ export function createBuildWorkflowTool(context: InstanceAiContext) { const targetWorkflowId = binding.workflowId; // Only the folder-enabled schema carries the field; the narrowing keeps the // handler valid for both shapes without a cast. + // Blank or "/" names no folder: OpenAI's strict tool schemas make models fill every field. const folderPath = - 'folderPath' in input && typeof input.folderPath === 'string' + 'folderPath' in input && + typeof input.folderPath === 'string' && + /[^\s/]/.test(input.folderPath) ? input.folderPath : undefined; if (folderPath !== undefined && targetWorkflowId) {