diff --git a/apps/sim/app/api/workflows/[id]/execute/route.ts b/apps/sim/app/api/workflows/[id]/execute/route.ts index b6e1aeab7b..34b99b1674 100644 --- a/apps/sim/app/api/workflows/[id]/execute/route.ts +++ b/apps/sim/app/api/workflows/[id]/execute/route.ts @@ -61,7 +61,7 @@ import type { SerializableExecutionState, } from '@/executor/execution/types' import type { NormalizedBlockOutput, StreamingExecution } from '@/executor/types' -import { hasExecutionResult } from '@/executor/utils/errors' +import { getExecutionErrorStatus, hasExecutionResult } from '@/executor/utils/errors' import { Serializer } from '@/serializer' import { CORE_TRIGGER_TYPES, type CoreTriggerType } from '@/stores/logs/filters/types' @@ -821,6 +821,7 @@ async function handleExecutePost( reqLogger.error(`Non-SSE execution failed: ${errorMessage}`) const executionResult = hasExecutionResult(error) ? error.executionResult : undefined + const status = getExecutionErrorStatus(error) return NextResponse.json( { @@ -835,7 +836,7 @@ async function handleExecutePost( } : undefined, }, - { status: 500 } + { status } ) } finally { timeoutController.cleanup() diff --git a/apps/sim/executor/utils/block-reference.test.ts b/apps/sim/executor/utils/block-reference.test.ts index 72ec514765..25e838b2c6 100644 --- a/apps/sim/executor/utils/block-reference.test.ts +++ b/apps/sim/executor/utils/block-reference.test.ts @@ -293,6 +293,7 @@ describe('InvalidFieldError', () => { expect(error.fieldPath).toBe('invalid.path') expect(error.availableFields).toEqual(['field1', 'field2']) expect(error.name).toBe('InvalidFieldError') + expect(error.statusCode).toBe(400) }) it('should format message correctly', () => { diff --git a/apps/sim/executor/utils/block-reference.ts b/apps/sim/executor/utils/block-reference.ts index e1a8b2200c..edf909a6d3 100644 --- a/apps/sim/executor/utils/block-reference.ts +++ b/apps/sim/executor/utils/block-reference.ts @@ -27,6 +27,8 @@ export interface BlockReferenceResult { } export class InvalidFieldError extends Error { + readonly statusCode = 400 + constructor( public readonly blockName: string, public readonly fieldPath: string, diff --git a/apps/sim/executor/utils/errors.ts b/apps/sim/executor/utils/errors.ts index 17137730a0..edcff8342e 100644 --- a/apps/sim/executor/utils/errors.ts +++ b/apps/sim/executor/utils/errors.ts @@ -49,6 +49,8 @@ export function buildBlockExecutionError(details: BlockExecutionErrorDetails): E const error = new Error(`${blockName}: ${errorMessage}`) + const innerStatusCode = readStatusCode(details.error) + Object.assign(error, { blockId: details.block.id, blockName, @@ -56,6 +58,7 @@ export function buildBlockExecutionError(details: BlockExecutionErrorDetails): E workflowId: details.context?.workflowId, timestamp: new Date().toISOString(), ...details.additionalInfo, + ...(innerStatusCode !== undefined ? { statusCode: innerStatusCode } : {}), }) return error @@ -89,6 +92,25 @@ export function buildHTTPError(config: { return error } +function readStatusCode(value: unknown): number | undefined { + if (!(value instanceof Error)) return undefined + const status = (value as unknown as { statusCode?: unknown }).statusCode + return typeof status === 'number' ? status : undefined +} + +/** + * Maps an execution error to an HTTP status code. Errors thrown from the + * executor that represent workflow-author mistakes (invalid field references, + * etc.) carry a 4xx `statusCode`; everything else is a 500. + */ +export function getExecutionErrorStatus(error: unknown): number { + const status = readStatusCode(error) + if (status !== undefined && status >= 400 && status < 500) { + return status + } + return 500 +} + export function normalizeError(error: unknown): string { if (error instanceof Error) { return error.message diff --git a/apps/sim/executor/variables/resolvers/block.ts b/apps/sim/executor/variables/resolvers/block.ts index 5228261531..e1a5be03f7 100644 --- a/apps/sim/executor/variables/resolvers/block.ts +++ b/apps/sim/executor/variables/resolvers/block.ts @@ -97,7 +97,6 @@ export class BlockResolver implements Resolver { if (fallback !== undefined) { return fallback } - throw new Error(error.message) } throw error }