From f84413516a80c87f2dca194306f5c1ed18ac2d11 Mon Sep 17 00:00:00 2001 From: Anne Aguirre Date: Fri, 16 Oct 2026 12:00:35 +0000 Subject: [PATCH] fix(core): Web search misconfigs in agents as UserError (#41257) --- .../agents/__tests__/from-json-config.test.ts | 65 ++++++++++--------- .../agents/json-config/from-json-config.ts | 4 +- 2 files changed, 38 insertions(+), 31 deletions(-) diff --git a/packages/cli/src/modules/agents/__tests__/from-json-config.test.ts b/packages/cli/src/modules/agents/__tests__/from-json-config.test.ts index e21ffdbce51..61c81733a49 100644 --- a/packages/cli/src/modules/agents/__tests__/from-json-config.test.ts +++ b/packages/cli/src/modules/agents/__tests__/from-json-config.test.ts @@ -8,6 +8,7 @@ import { type AgentJsonConfig, } from '@n8n/api-types'; import type { JSONSchema7 } from 'json-schema'; +import { UserError } from 'n8n-workflow'; import { buildFromJson, buildProviderToolsForModel } from '../json-config/from-json-config'; import type { ToolExecutor } from '../json-config/from-json-config'; @@ -826,21 +827,24 @@ describe('buildFromJson()', () => { }); it('rejects native web search config for unsupported providers without fallback settings', async () => { - await expect( - buildFromJson( - makeConfig({ - model: 'google/gemini-2.5-flash', - config: { webSearch: { enabled: true } }, - providerTools: { 'anthropic.web_search': { maxUses: 5 } }, - }), - {}, - { - toolExecutor: makeMockToolExecutor(), - credentialProvider: makeMockCredentialProvider(), - memoryFactory: makeMockMemoryFactory(), - }, - ), - ).rejects.toThrow('Web search is enabled but no fallback search provider is configured.'); + const rejected = buildFromJson( + makeConfig({ + model: 'google/gemini-2.5-flash', + config: { webSearch: { enabled: true } }, + providerTools: { 'anthropic.web_search': { maxUses: 5 } }, + }), + {}, + { + toolExecutor: makeMockToolExecutor(), + credentialProvider: makeMockCredentialProvider(), + memoryFactory: makeMockMemoryFactory(), + }, + ); + + await expect(rejected).rejects.toThrow(UserError); + await expect(rejected).rejects.toThrow( + 'Web search is enabled but no fallback search provider is configured.', + ); }); it('does not enable native web search when config is sparse', async () => { @@ -1008,20 +1012,23 @@ describe('buildFromJson()', () => { }); it('requires fallback web search credentials for providers without native web search', async () => { - await expect( - buildFromJson( - makeConfig({ - model: 'deepseek/deepseek-chat', - config: { webSearch: { enabled: true, provider: 'brave' } }, - }), - {}, - { - toolExecutor: makeMockToolExecutor(), - credentialProvider: makeMockCredentialProvider(), - memoryFactory: makeMockMemoryFactory(), - }, - ), - ).rejects.toThrow('Web search is enabled but no search credential is configured.'); + const rejected = buildFromJson( + makeConfig({ + model: 'deepseek/deepseek-chat', + config: { webSearch: { enabled: true, provider: 'brave' } }, + }), + {}, + { + toolExecutor: makeMockToolExecutor(), + credentialProvider: makeMockCredentialProvider(), + memoryFactory: makeMockMemoryFactory(), + }, + ); + + await expect(rejected).rejects.toThrow(UserError); + await expect(rejected).rejects.toThrow( + 'Web search is enabled but no search credential is configured.', + ); }); it('adds fallback web search tool for the n8n Connect managed credential (brave)', async () => { diff --git a/packages/cli/src/modules/agents/json-config/from-json-config.ts b/packages/cli/src/modules/agents/json-config/from-json-config.ts index b3b6a3fc296..3838560d89c 100644 --- a/packages/cli/src/modules/agents/json-config/from-json-config.ts +++ b/packages/cli/src/modules/agents/json-config/from-json-config.ts @@ -326,10 +326,10 @@ function buildFallbackWebSearchTool( }; } if (webSearchConfig.provider !== 'brave' && webSearchConfig.provider !== 'searxng') { - throw new Error('Web search is enabled but no fallback search provider is configured.'); + throw new UserError('Web search is enabled but no fallback search provider is configured.'); } if (!webSearchConfig.credential) { - throw new Error('Web search is enabled but no search credential is configured.'); + throw new UserError('Web search is enabled but no search credential is configured.'); } const credentialId = webSearchConfig.credential;