From da6e08eecc5afaf1cfaa8f49a2bac3321cd65e4a Mon Sep 17 00:00:00 2001 From: Pierre Merlet Date: Sat, 20 Jun 2026 15:59:46 +0200 Subject: [PATCH] fix(agent): explicit error when workflow executor URL is not configured MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The /_internal/workflow-executions proxy route was only mounted when `workflowExecutorUrl` was set, so hitting it on an agent without that option returned a bare koa 404 with an empty body — indistinguishable from a real "run not found" and giving the client no hint about the misconfiguration. Mount the route unconditionally and throw an InternalServerError with an explicit message when `workflowExecutorUrl` is missing, so the client gets a clear 500 detail instead of a silent 404. Co-Authored-By: Claude Opus 4.8 --- packages/agent/src/routes/index.ts | 4 +-- .../workflow/workflow-executor-proxy.ts | 13 +++++++-- packages/agent/test/routes/index.test.ts | 6 +++- .../workflow/workflow-executor-proxy.test.ts | 28 +++++++++++++++++++ 4 files changed, 46 insertions(+), 5 deletions(-) diff --git a/packages/agent/src/routes/index.ts b/packages/agent/src/routes/index.ts index 6d626180e2..39c748581c 100644 --- a/packages/agent/src/routes/index.ts +++ b/packages/agent/src/routes/index.ts @@ -174,8 +174,8 @@ function getAiRoutes(options: Options, services: Services, aiRouter: AiRouter | } function getWorkflowExecutorRoutes(options: Options, services: Services): BaseRoute[] { - if (!options.workflowExecutorUrl) return []; - + // Always mount the route so that hitting it without `workflowExecutorUrl` configured + // returns an explicit error to the client instead of a bare 404. return [new WorkflowExecutorProxyRoute(services, options)]; } diff --git a/packages/agent/src/routes/workflow/workflow-executor-proxy.ts b/packages/agent/src/routes/workflow/workflow-executor-proxy.ts index 0fc391cffe..f45c35598a 100644 --- a/packages/agent/src/routes/workflow/workflow-executor-proxy.ts +++ b/packages/agent/src/routes/workflow/workflow-executor-proxy.ts @@ -3,6 +3,7 @@ import type { AgentOptionsWithDefaults } from '../../types'; import type KoaRouter from '@koa/router'; import type { Context } from 'koa'; +import { InternalServerError } from '@forestadmin/datasource-toolkit'; import { request as httpRequest } from 'http'; import { request as httpsRequest } from 'https'; @@ -16,12 +17,14 @@ type ForwardedHeaders = { export default class WorkflowExecutorProxyRoute extends BaseRoute { readonly type = RouteType.PrivateRoute; - private readonly executorUrl: URL; + private readonly executorUrl: URL | null; constructor(services: ForestAdminHttpDriverServices, options: AgentOptionsWithDefaults) { super(services, options); // Remove trailing slash for clean URL joining - this.executorUrl = new URL(options.workflowExecutorUrl.replace(/\/+$/, '')); + this.executorUrl = options.workflowExecutorUrl + ? new URL(options.workflowExecutorUrl.replace(/\/+$/, '')) + : null; } setupRoutes(router: KoaRouter): void { @@ -30,6 +33,12 @@ export default class WorkflowExecutorProxyRoute extends BaseRoute { } private async handleProxy(context: Context): Promise { + if (!this.executorUrl) { + throw new InternalServerError( + 'The workflow executor is not configured on this agent: the `workflowExecutorUrl` option is missing.', + ); + } + const { runId } = context.params; const isTrigger = context.method === 'POST'; const qs = context.querystring ? `?${context.querystring}` : ''; diff --git a/packages/agent/test/routes/index.test.ts b/packages/agent/test/routes/index.test.ts index d2e56b8ddb..263856ffba 100644 --- a/packages/agent/test/routes/index.test.ts +++ b/packages/agent/test/routes/index.test.ts @@ -68,8 +68,12 @@ describe('Route index', () => { expect(NATIVE_QUERY_ROUTES_CTOR).toEqual([DataSourceNativeQueryRoute]); }); + const WORKFLOW_EXECUTOR_ROUTE_SIZE = 1; const BASE_ROUTE_SIZE = - ROOT_ROUTES_CTOR.length + CAPABILITIES_ROUTES_CTOR.length + NATIVE_QUERY_ROUTES_CTOR.length; + ROOT_ROUTES_CTOR.length + + CAPABILITIES_ROUTES_CTOR.length + + NATIVE_QUERY_ROUTES_CTOR.length + + WORKFLOW_EXECUTOR_ROUTE_SIZE; describe('makeRoutes', () => { describe('when a data source without relations', () => { diff --git a/packages/agent/test/routes/workflow/workflow-executor-proxy.test.ts b/packages/agent/test/routes/workflow/workflow-executor-proxy.test.ts index d5cbad16b1..9d279b3412 100644 --- a/packages/agent/test/routes/workflow/workflow-executor-proxy.test.ts +++ b/packages/agent/test/routes/workflow/workflow-executor-proxy.test.ts @@ -73,6 +73,16 @@ describe('WorkflowExecutorProxyRoute', () => { expect(route.type).toBe(RouteType.PrivateRoute); }); + + test('should not throw when workflowExecutorUrl is not configured', () => { + expect( + () => + new WorkflowExecutorProxyRoute( + services, + factories.forestAdminHttpDriverOptions.build({ workflowExecutorUrl: null }), + ), + ).not.toThrow(); + }); }); describe('setupRoutes', () => { @@ -224,6 +234,24 @@ describe('WorkflowExecutorProxyRoute', () => { ).rejects.toThrow(); }); + test('should throw an explicit error when workflowExecutorUrl is not configured', async () => { + const route = new WorkflowExecutorProxyRoute( + services, + factories.forestAdminHttpDriverOptions.build({ workflowExecutorUrl: null }), + ); + + const context = createMockContext({ + customProperties: { params: { runId: 'run-123' } }, + }); + Object.defineProperty(context, 'url', { + value: '/_internal/workflow-executions/run-123', + }); + + await expect( + (route as unknown as { handleProxy: (ctx: unknown) => Promise }).handleProxy(context), + ).rejects.toThrow('The workflow executor is not configured on this agent'); + }); + test('should forward query params to the executor', async () => { const route = new WorkflowExecutorProxyRoute( services,