From 6caa79b4aca012113335fadace0f3f9de53353e6 Mon Sep 17 00:00:00 2001 From: Theodore Li Date: Tue, 17 Mar 2026 12:10:24 -0700 Subject: [PATCH 1/5] fix(mothership): mothership-ran workflows show workflow validation errors --- .../utils/workflow-execution-utils.ts | 62 +++++++++++++++++-- 1 file changed, 57 insertions(+), 5 deletions(-) diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/utils/workflow-execution-utils.ts b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/utils/workflow-execution-utils.ts index c0ca16cc9af..749490eb380 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/utils/workflow-execution-utils.ts +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/utils/workflow-execution-utils.ts @@ -436,7 +436,7 @@ export async function executeWorkflowWithFullLogging( } const executionId = options.executionId || uuidv4() - const { addConsole, updateConsole } = useTerminalConsoleStore.getState() + const { addConsole, updateConsole, cancelRunningEntries } = useTerminalConsoleStore.getState() const { setActiveBlocks, setBlockRunStatus, setEdgeRunStatus, setCurrentExecutionId } = useExecutionStore.getState() const wfId = targetWorkflowId @@ -445,6 +445,7 @@ export async function executeWorkflowWithFullLogging( const activeBlocksSet = new Set() const activeBlockRefCounts = new Map() const executionIdRef = { current: executionId } + const accumulatedBlockLogs: BlockLog[] = [] const blockHandlers = createBlockEventHandlers( { @@ -453,7 +454,7 @@ export async function executeWorkflowWithFullLogging( workflowEdges, activeBlocksSet, activeBlockRefCounts, - accumulatedBlockLogs: [], + accumulatedBlockLogs, accumulatedBlockStates: new Map(), executedBlockIds: new Set(), consoleMode: 'update', @@ -490,7 +491,24 @@ export async function executeWorkflowWithFullLogging( if (!response.ok) { const error = await response.json() - throw new Error(error.error || 'Workflow execution failed') + const errorMessage = error.error || 'Workflow execution failed' + const now = new Date().toISOString() + addConsole({ + input: {}, + output: {}, + success: false, + error: errorMessage, + durationMs: 0, + startedAt: now, + executionOrder: 0, + endedAt: now, + workflowId: wfId, + blockId: 'execution-error', + executionId, + blockName: 'Execution Error', + blockType: 'error', + }) + throw new Error(errorMessage) } if (!response.body) { @@ -575,15 +593,49 @@ export async function executeWorkflowWithFullLogging( } break - case 'execution:error': + case 'execution:error': { setCurrentExecutionId(wfId, null) + const errorMessage = event.data.error || 'Execution failed' executionResult = { success: false, output: {}, - error: event.data.error || 'Execution failed', + error: errorMessage, logs: [], } + + cancelRunningEntries(wfId) + + const isPreExecutionError = accumulatedBlockLogs.length === 0 + const hasBlockError = accumulatedBlockLogs.some((log) => log.error) + if (isPreExecutionError || !hasBlockError) { + const isTimeout = errorMessage.toLowerCase().includes('timed out') + const now = new Date().toISOString() + addConsole({ + input: {}, + output: {}, + success: false, + error: errorMessage, + durationMs: event.data.duration || 0, + startedAt: now, + executionOrder: isPreExecutionError ? 0 : Number.MAX_SAFE_INTEGER, + endedAt: now, + workflowId: wfId, + blockId: isPreExecutionError + ? 'validation' + : isTimeout + ? 'timeout-error' + : 'execution-error', + executionId: executionIdRef.current, + blockName: isPreExecutionError + ? 'Workflow Validation' + : isTimeout + ? 'Timeout Error' + : 'Execution Error', + blockType: isPreExecutionError ? 'validation' : 'error', + }) + } break + } } } } From 9762bbaaae7293bc0ce5b99a7d73f665a45109de Mon Sep 17 00:00:00 2001 From: Theodore Li Date: Tue, 17 Mar 2026 15:32:53 -0700 Subject: [PATCH 2/5] Distinguish errors from 5xxs --- .../w/[workflowId]/utils/workflow-execution-utils.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/utils/workflow-execution-utils.ts b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/utils/workflow-execution-utils.ts index 749490eb380..31a9dac993f 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/utils/workflow-execution-utils.ts +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/utils/workflow-execution-utils.ts @@ -492,6 +492,7 @@ export async function executeWorkflowWithFullLogging( if (!response.ok) { const error = await response.json() const errorMessage = error.error || 'Workflow execution failed' + const isValidationError = response.status >= 400 && response.status < 500 const now = new Date().toISOString() addConsole({ input: {}, @@ -503,10 +504,10 @@ export async function executeWorkflowWithFullLogging( executionOrder: 0, endedAt: now, workflowId: wfId, - blockId: 'execution-error', + blockId: isValidationError ? 'validation' : 'execution-error', executionId, - blockName: 'Execution Error', - blockType: 'error', + blockName: isValidationError ? 'Workflow Validation' : 'Execution Error', + blockType: isValidationError ? 'validation' : 'error', }) throw new Error(errorMessage) } From 52a1880a0036554098b2db61060c1f60889d16c1 Mon Sep 17 00:00:00 2001 From: Theodore Li Date: Tue, 17 Mar 2026 17:22:39 -0700 Subject: [PATCH 3/5] Unify workflow event handling --- .../hooks/use-workflow-execution.ts | 174 +++------ .../utils/workflow-execution-utils.ts | 342 +++++++++++------- apps/sim/hooks/use-execution-stream.ts | 9 +- 3 files changed, 270 insertions(+), 255 deletions(-) diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/hooks/use-workflow-execution.ts b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/hooks/use-workflow-execution.ts index 0943d3e04b4..10cef4f3ab4 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/hooks/use-workflow-execution.ts +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/hooks/use-workflow-execution.ts @@ -17,7 +17,11 @@ import { import { useCurrentWorkflow } from '@/app/workspace/[workspaceId]/w/[workflowId]/hooks/use-current-workflow' import { type BlockEventHandlerConfig, + addHttpErrorConsoleEntry, + addExecutionErrorConsoleEntry as sharedAddExecutionErrorConsoleEntry, createBlockEventHandlers, + handleExecutionCancelledConsole as sharedHandleExecutionCancelledConsole, + handleExecutionErrorConsole as sharedHandleExecutionErrorConsole, } from '@/app/workspace/[workspaceId]/w/[workflowId]/utils/workflow-execution-utils' import { getBlock } from '@/blocks' import type { SerializableExecutionState } from '@/executor/execution/types' @@ -159,22 +163,7 @@ export function useWorkflowExecution() { setActiveBlocks, ]) - /** - * Builds timing fields for execution-level console entries. - */ - const buildExecutionTiming = useCallback((durationMs?: number) => { - const normalizedDuration = durationMs || 0 - return { - durationMs: normalizedDuration, - startedAt: new Date(Date.now() - normalizedDuration).toISOString(), - endedAt: new Date().toISOString(), - } - }, []) - - /** - * Adds an execution-level error entry to the console when appropriate. - */ - const addExecutionErrorConsoleEntry = useCallback( + const handleExecutionErrorConsole = useCallback( (params: { workflowId?: string executionId?: string @@ -184,102 +173,23 @@ export function useWorkflowExecution() { isPreExecutionError?: boolean }) => { if (!params.workflowId) return - - const hasBlockError = params.blockLogs.some((log) => log.error) - const isPreExecutionError = params.isPreExecutionError ?? false - if (!isPreExecutionError && hasBlockError) { - return - } - - const errorMessage = params.error || 'Execution failed' - const isTimeout = errorMessage.toLowerCase().includes('timed out') - const timing = buildExecutionTiming(params.durationMs) - - addConsole({ - input: {}, - output: {}, - success: false, - error: errorMessage, - durationMs: timing.durationMs, - startedAt: timing.startedAt, - executionOrder: isPreExecutionError ? 0 : Number.MAX_SAFE_INTEGER, - endedAt: timing.endedAt, + sharedHandleExecutionErrorConsole(addConsole, cancelRunningEntries, { + ...params, workflowId: params.workflowId, - blockId: isPreExecutionError - ? 'validation' - : isTimeout - ? 'timeout-error' - : 'execution-error', - executionId: params.executionId, - blockName: isPreExecutionError - ? 'Workflow Validation' - : isTimeout - ? 'Timeout Error' - : 'Execution Error', - blockType: isPreExecutionError ? 'validation' : 'error', }) }, - [addConsole, buildExecutionTiming] + [addConsole, cancelRunningEntries] ) - /** - * Adds an execution-level cancellation entry to the console. - */ - const addExecutionCancelledConsoleEntry = useCallback( + const handleExecutionCancelledConsole = useCallback( (params: { workflowId?: string; executionId?: string; durationMs?: number }) => { if (!params.workflowId) return - - const timing = buildExecutionTiming(params.durationMs) - addConsole({ - input: {}, - output: {}, - success: false, - error: 'Execution was cancelled', - durationMs: timing.durationMs, - startedAt: timing.startedAt, - executionOrder: Number.MAX_SAFE_INTEGER, - endedAt: timing.endedAt, + sharedHandleExecutionCancelledConsole(addConsole, cancelRunningEntries, { + ...params, workflowId: params.workflowId, - blockId: 'cancelled', - executionId: params.executionId, - blockName: 'Execution Cancelled', - blockType: 'cancelled', }) }, - [addConsole, buildExecutionTiming] - ) - - /** - * Handles workflow-level execution errors for console output. - */ - const handleExecutionErrorConsole = useCallback( - (params: { - workflowId?: string - executionId?: string - error?: string - durationMs?: number - blockLogs: BlockLog[] - isPreExecutionError?: boolean - }) => { - if (params.workflowId) { - cancelRunningEntries(params.workflowId) - } - addExecutionErrorConsoleEntry(params) - }, - [addExecutionErrorConsoleEntry, cancelRunningEntries] - ) - - /** - * Handles workflow-level execution cancellations for console output. - */ - const handleExecutionCancelledConsole = useCallback( - (params: { workflowId?: string; executionId?: string; durationMs?: number }) => { - if (params.workflowId) { - cancelRunningEntries(params.workflowId) - } - addExecutionCancelledConsoleEntry(params) - }, - [addExecutionCancelledConsoleEntry, cancelRunningEntries] + [addConsole, cancelRunningEntries] ) const buildBlockEventHandlers = useCallback( @@ -1319,31 +1229,43 @@ export function useWorkflowExecution() { } else { if (!executor) { try { - let blockId = 'serialization' - let blockName = 'Workflow' - let blockType = 'serializer' - if (error instanceof WorkflowValidationError) { - blockId = error.blockId || blockId - blockName = error.blockName || blockName - blockType = error.blockType || blockType + const httpStatus = isRecord(error) && typeof error.httpStatus === 'number' + ? error.httpStatus + : undefined + const storeAddConsole = useTerminalConsoleStore.getState().addConsole + + if (httpStatus && activeWorkflowId) { + addHttpErrorConsoleEntry(storeAddConsole, { + workflowId: activeWorkflowId, + executionId: options?.executionId, + error: normalizedMessage, + httpStatus, + }) + } else if (error instanceof WorkflowValidationError) { + storeAddConsole({ + input: {}, + output: {}, + success: false, + error: normalizedMessage, + durationMs: 0, + startedAt: new Date().toISOString(), + executionOrder: Number.MAX_SAFE_INTEGER, + endedAt: new Date().toISOString(), + workflowId: activeWorkflowId || '', + blockId: error.blockId || 'serialization', + executionId: options?.executionId, + blockName: error.blockName || 'Workflow', + blockType: error.blockType || 'serializer', + }) + } else { + sharedAddExecutionErrorConsoleEntry(storeAddConsole, { + workflowId: activeWorkflowId || '', + executionId: options?.executionId, + error: normalizedMessage, + blockLogs: [], + isPreExecutionError: true, + }) } - - // Use MAX_SAFE_INTEGER so execution errors appear at the end of the log - useTerminalConsoleStore.getState().addConsole({ - input: {}, - output: {}, - success: false, - error: normalizedMessage, - durationMs: 0, - startedAt: new Date().toISOString(), - executionOrder: Number.MAX_SAFE_INTEGER, - endedAt: new Date().toISOString(), - workflowId: activeWorkflowId || '', - blockId, - executionId: options?.executionId, - blockName, - blockType, - }) } catch {} } diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/utils/workflow-execution-utils.ts b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/utils/workflow-execution-utils.ts index 31a9dac993f..2c40e52b0d4 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/utils/workflow-execution-utils.ts +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/utils/workflow-execution-utils.ts @@ -13,6 +13,7 @@ import type { StreamingExecution, } from '@/executor/types' import { stripCloneSuffixes } from '@/executor/utils/subflow-utils' +import { processSSEStream } from '@/hooks/use-execution-stream' const logger = createLogger('workflow-execution-utils') @@ -406,6 +407,161 @@ export function createBlockEventHandlers( return { onBlockStarted, onBlockCompleted, onBlockError, onBlockChildWorkflowStarted } } +type AddConsoleFn = (entry: Omit) => ConsoleEntry +type CancelRunningEntriesFn = (workflowId: string) => void + +export interface ExecutionTimingFields { + durationMs: number + startedAt: string + endedAt: string +} + +/** + * Builds timing fields for an execution-level console entry. + */ +export function buildExecutionTiming(durationMs?: number): ExecutionTimingFields { + const normalizedDuration = durationMs || 0 + return { + durationMs: normalizedDuration, + startedAt: new Date(Date.now() - normalizedDuration).toISOString(), + endedAt: new Date().toISOString(), + } +} + +export interface ExecutionErrorConsoleParams { + workflowId: string + executionId?: string + error?: string + durationMs?: number + blockLogs: BlockLog[] + isPreExecutionError?: boolean +} + +/** + * Adds an execution-level error entry to the console when no block-level error already covers it. + * Shared between direct user execution and mothership-initiated execution. + */ +export function addExecutionErrorConsoleEntry( + addConsole: AddConsoleFn, + params: ExecutionErrorConsoleParams +): void { + const hasBlockError = params.blockLogs.some((log) => log.error) + const isPreExecutionError = params.isPreExecutionError ?? false + if (!isPreExecutionError && hasBlockError) return + + const errorMessage = params.error || 'Execution failed' + const isTimeout = errorMessage.toLowerCase().includes('timed out') + const timing = buildExecutionTiming(params.durationMs) + + addConsole({ + input: {}, + output: {}, + success: false, + error: errorMessage, + durationMs: timing.durationMs, + startedAt: timing.startedAt, + executionOrder: isPreExecutionError ? 0 : Number.MAX_SAFE_INTEGER, + endedAt: timing.endedAt, + workflowId: params.workflowId, + blockId: isPreExecutionError ? 'validation' : isTimeout ? 'timeout-error' : 'execution-error', + executionId: params.executionId, + blockName: isPreExecutionError + ? 'Workflow Validation' + : isTimeout + ? 'Timeout Error' + : 'Execution Error', + blockType: isPreExecutionError ? 'validation' : 'error', + }) +} + +/** + * Cancels running entries and adds an execution-level error console entry. + */ +export function handleExecutionErrorConsole( + addConsole: AddConsoleFn, + cancelRunningEntries: CancelRunningEntriesFn, + params: ExecutionErrorConsoleParams +): void { + cancelRunningEntries(params.workflowId) + addExecutionErrorConsoleEntry(addConsole, params) +} + +export interface HttpErrorConsoleParams { + workflowId: string + executionId?: string + error: string + httpStatus: number +} + +/** + * Adds a console entry for HTTP-level execution errors (non-OK response before SSE streaming). + */ +export function addHttpErrorConsoleEntry( + addConsole: AddConsoleFn, + params: HttpErrorConsoleParams +): void { + const isValidationError = params.httpStatus >= 400 && params.httpStatus < 500 + const now = new Date().toISOString() + addConsole({ + input: {}, + output: {}, + success: false, + error: params.error, + durationMs: 0, + startedAt: now, + executionOrder: 0, + endedAt: now, + workflowId: params.workflowId, + blockId: isValidationError ? 'validation' : 'execution-error', + executionId: params.executionId, + blockName: isValidationError ? 'Workflow Validation' : 'Execution Error', + blockType: isValidationError ? 'validation' : 'error', + }) +} + +export interface CancelledConsoleParams { + workflowId: string + executionId?: string + durationMs?: number +} + +/** + * Adds a console entry for execution cancellation. + */ +export function addCancelledConsoleEntry( + addConsole: AddConsoleFn, + params: CancelledConsoleParams +): void { + const timing = buildExecutionTiming(params.durationMs) + addConsole({ + input: {}, + output: {}, + success: false, + error: 'Execution was cancelled', + durationMs: timing.durationMs, + startedAt: timing.startedAt, + executionOrder: Number.MAX_SAFE_INTEGER, + endedAt: timing.endedAt, + workflowId: params.workflowId, + blockId: 'cancelled', + executionId: params.executionId, + blockName: 'Execution Cancelled', + blockType: 'cancelled', + }) +} + +/** + * Cancels running entries and adds a cancelled console entry. + */ +export function handleExecutionCancelledConsole( + addConsole: AddConsoleFn, + cancelRunningEntries: CancelRunningEntriesFn, + params: CancelledConsoleParams +): void { + cancelRunningEntries(params.workflowId) + addCancelledConsoleEntry(addConsole, params) +} + export interface WorkflowExecutionOptions { workflowId?: string workflowInput?: any @@ -492,22 +648,11 @@ export async function executeWorkflowWithFullLogging( if (!response.ok) { const error = await response.json() const errorMessage = error.error || 'Workflow execution failed' - const isValidationError = response.status >= 400 && response.status < 500 - const now = new Date().toISOString() - addConsole({ - input: {}, - output: {}, - success: false, - error: errorMessage, - durationMs: 0, - startedAt: now, - executionOrder: 0, - endedAt: now, + addHttpErrorConsoleEntry(addConsole, { workflowId: wfId, - blockId: isValidationError ? 'validation' : 'execution-error', executionId, - blockName: isValidationError ? 'Workflow Validation' : 'Execution Error', - blockType: isValidationError ? 'validation' : 'error', + error: errorMessage, + httpStatus: response.status, }) throw new Error(errorMessage) } @@ -516,9 +661,12 @@ export async function executeWorkflowWithFullLogging( throw new Error('No response body') } - const reader = response.body.getReader() - const decoder = new TextDecoder() - let buffer = '' + const serverExecutionId = response.headers.get('X-Execution-Id') + if (serverExecutionId) { + executionIdRef.current = serverExecutionId + setCurrentExecutionId(wfId, serverExecutionId) + } + let executionResult: ExecutionResult = { success: false, output: {}, @@ -526,123 +674,63 @@ export async function executeWorkflowWithFullLogging( } try { - while (true) { - const { done, value } = await reader.read() - if (done) break - - buffer += decoder.decode(value, { stream: true }) - const lines = buffer.split('\n\n') - buffer = lines.pop() || '' - - for (const line of lines) { - if (!line.trim() || !line.startsWith('data: ')) continue + await processSSEStream(response.body.getReader(), { + onExecutionStarted: (data) => { + logger.info('Execution started', { startTime: data.startTime }) + }, - const data = line.substring(6).trim() - if (data === '[DONE]') continue + onBlockStarted: blockHandlers.onBlockStarted, + onBlockCompleted: blockHandlers.onBlockCompleted, + onBlockError: blockHandlers.onBlockError, + onBlockChildWorkflowStarted: blockHandlers.onBlockChildWorkflowStarted, + + onExecutionCompleted: (data) => { + setCurrentExecutionId(wfId, null) + executionResult = { + success: data.success, + output: data.output, + logs: accumulatedBlockLogs, + metadata: { + duration: data.duration, + startTime: data.startTime, + endTime: data.endTime, + }, + } + }, - let event: any - try { - event = JSON.parse(data) - } catch { - continue + onExecutionCancelled: () => { + setCurrentExecutionId(wfId, null) + executionResult = { + success: false, + output: {}, + error: 'Execution was cancelled', + logs: accumulatedBlockLogs, } + }, - switch (event.type) { - case 'execution:started': { - setCurrentExecutionId(wfId, event.executionId) - executionIdRef.current = event.executionId || executionId - break - } - - case 'block:started': - blockHandlers.onBlockStarted(event.data) - break - - case 'block:completed': - blockHandlers.onBlockCompleted(event.data) - break - - case 'block:error': - blockHandlers.onBlockError(event.data) - break - - case 'block:childWorkflowStarted': - blockHandlers.onBlockChildWorkflowStarted(event.data) - break - - case 'execution:completed': - setCurrentExecutionId(wfId, null) - executionResult = { - success: event.data.success, - output: event.data.output, - logs: [], - metadata: { - duration: event.data.duration, - startTime: event.data.startTime, - endTime: event.data.endTime, - }, - } - break - - case 'execution:cancelled': - setCurrentExecutionId(wfId, null) - executionResult = { - success: false, - output: {}, - error: 'Execution was cancelled', - logs: [], - } - break - - case 'execution:error': { - setCurrentExecutionId(wfId, null) - const errorMessage = event.data.error || 'Execution failed' - executionResult = { - success: false, - output: {}, - error: errorMessage, - logs: [], - } - - cancelRunningEntries(wfId) - - const isPreExecutionError = accumulatedBlockLogs.length === 0 - const hasBlockError = accumulatedBlockLogs.some((log) => log.error) - if (isPreExecutionError || !hasBlockError) { - const isTimeout = errorMessage.toLowerCase().includes('timed out') - const now = new Date().toISOString() - addConsole({ - input: {}, - output: {}, - success: false, - error: errorMessage, - durationMs: event.data.duration || 0, - startedAt: now, - executionOrder: isPreExecutionError ? 0 : Number.MAX_SAFE_INTEGER, - endedAt: now, - workflowId: wfId, - blockId: isPreExecutionError - ? 'validation' - : isTimeout - ? 'timeout-error' - : 'execution-error', - executionId: executionIdRef.current, - blockName: isPreExecutionError - ? 'Workflow Validation' - : isTimeout - ? 'Timeout Error' - : 'Execution Error', - blockType: isPreExecutionError ? 'validation' : 'error', - }) - } - break - } + onExecutionError: (data) => { + setCurrentExecutionId(wfId, null) + const errorMessage = data.error || 'Execution failed' + executionResult = { + success: false, + output: {}, + error: errorMessage, + logs: accumulatedBlockLogs, + metadata: { duration: data.duration }, } - } - } + + handleExecutionErrorConsole(addConsole, cancelRunningEntries, { + workflowId: wfId, + executionId: executionIdRef.current, + error: errorMessage, + durationMs: data.duration || 0, + blockLogs: accumulatedBlockLogs, + isPreExecutionError: accumulatedBlockLogs.length === 0, + }) + }, + }, 'CopilotExecution') } finally { setCurrentExecutionId(wfId, null) - reader.releaseLock() setActiveBlocks(wfId, new Set()) } diff --git a/apps/sim/hooks/use-execution-stream.ts b/apps/sim/hooks/use-execution-stream.ts index 12a7dc8cabf..36fd801db63 100644 --- a/apps/sim/hooks/use-execution-stream.ts +++ b/apps/sim/hooks/use-execution-stream.ts @@ -31,8 +31,9 @@ function isClientDisconnectError(error: any): boolean { /** * Processes SSE events from a response body and invokes appropriate callbacks. + * Exported for use by standalone (non-hook) execution paths like executeWorkflowWithFullLogging. */ -async function processSSEStream( +export async function processSSEStream( reader: ReadableStreamDefaultReader, callbacks: ExecutionStreamCallbacks, logPrefix: string @@ -198,6 +199,7 @@ export function useExecutionStream() { if (errorResponse && typeof errorResponse === 'object') { Object.assign(error, { executionResult: errorResponse }) } + Object.assign(error, { httpStatus: response.status }) throw error } @@ -267,12 +269,15 @@ export function useExecutionStream() { try { errorResponse = await response.json() } catch { - throw new Error(`Server error (${response.status}): ${response.statusText}`) + const error = new Error(`Server error (${response.status}): ${response.statusText}`) + Object.assign(error, { httpStatus: response.status }) + throw error } const error = new Error(errorResponse.error || 'Failed to start execution') if (errorResponse && typeof errorResponse === 'object') { Object.assign(error, { executionResult: errorResponse }) } + Object.assign(error, { httpStatus: response.status }) throw error } From 5751bc21d803244d599fb75d3cf785ab87571417 Mon Sep 17 00:00:00 2001 From: Theodore Li Date: Tue, 17 Mar 2026 17:31:43 -0700 Subject: [PATCH 4/5] Fix run from block --- .../w/[workflowId]/hooks/use-workflow-execution.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/hooks/use-workflow-execution.ts b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/hooks/use-workflow-execution.ts index 10cef4f3ab4..3c28029a03d 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/hooks/use-workflow-execution.ts +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/hooks/use-workflow-execution.ts @@ -1603,8 +1603,8 @@ export function useWorkflowExecution() { accumulatedBlockLogs, accumulatedBlockStates, executedBlockIds, - consoleMode: 'add', - includeStartConsoleEntry: false, + consoleMode: 'update', + includeStartConsoleEntry: true, }) await executionStream.executeFromBlock({ From 4dcc5ed414a47079c0bc16e3707f6d51a8a7459e Mon Sep 17 00:00:00 2001 From: Theodore Li Date: Wed, 18 Mar 2026 03:34:15 -0700 Subject: [PATCH 5/5] Fix lint --- .../hooks/use-workflow-execution.ts | 9 +- .../utils/workflow-execution-utils.ts | 112 +++++++++--------- 2 files changed, 62 insertions(+), 59 deletions(-) diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/hooks/use-workflow-execution.ts b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/hooks/use-workflow-execution.ts index 3c28029a03d..e29c3849038 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/hooks/use-workflow-execution.ts +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/hooks/use-workflow-execution.ts @@ -16,10 +16,10 @@ import { } from '@/lib/workflows/triggers/triggers' import { useCurrentWorkflow } from '@/app/workspace/[workspaceId]/w/[workflowId]/hooks/use-current-workflow' import { - type BlockEventHandlerConfig, addHttpErrorConsoleEntry, - addExecutionErrorConsoleEntry as sharedAddExecutionErrorConsoleEntry, + type BlockEventHandlerConfig, createBlockEventHandlers, + addExecutionErrorConsoleEntry as sharedAddExecutionErrorConsoleEntry, handleExecutionCancelledConsole as sharedHandleExecutionCancelledConsole, handleExecutionErrorConsole as sharedHandleExecutionErrorConsole, } from '@/app/workspace/[workspaceId]/w/[workflowId]/utils/workflow-execution-utils' @@ -1229,9 +1229,8 @@ export function useWorkflowExecution() { } else { if (!executor) { try { - const httpStatus = isRecord(error) && typeof error.httpStatus === 'number' - ? error.httpStatus - : undefined + const httpStatus = + isRecord(error) && typeof error.httpStatus === 'number' ? error.httpStatus : undefined const storeAddConsole = useTerminalConsoleStore.getState().addConsole if (httpStatus && activeWorkflowId) { diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/utils/workflow-execution-utils.ts b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/utils/workflow-execution-utils.ts index 2c40e52b0d4..32d286ddf56 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/utils/workflow-execution-utils.ts +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/utils/workflow-execution-utils.ts @@ -674,61 +674,65 @@ export async function executeWorkflowWithFullLogging( } try { - await processSSEStream(response.body.getReader(), { - onExecutionStarted: (data) => { - logger.info('Execution started', { startTime: data.startTime }) - }, - - onBlockStarted: blockHandlers.onBlockStarted, - onBlockCompleted: blockHandlers.onBlockCompleted, - onBlockError: blockHandlers.onBlockError, - onBlockChildWorkflowStarted: blockHandlers.onBlockChildWorkflowStarted, - - onExecutionCompleted: (data) => { - setCurrentExecutionId(wfId, null) - executionResult = { - success: data.success, - output: data.output, - logs: accumulatedBlockLogs, - metadata: { - duration: data.duration, - startTime: data.startTime, - endTime: data.endTime, - }, - } - }, - - onExecutionCancelled: () => { - setCurrentExecutionId(wfId, null) - executionResult = { - success: false, - output: {}, - error: 'Execution was cancelled', - logs: accumulatedBlockLogs, - } - }, - - onExecutionError: (data) => { - setCurrentExecutionId(wfId, null) - const errorMessage = data.error || 'Execution failed' - executionResult = { - success: false, - output: {}, - error: errorMessage, - logs: accumulatedBlockLogs, - metadata: { duration: data.duration }, - } - - handleExecutionErrorConsole(addConsole, cancelRunningEntries, { - workflowId: wfId, - executionId: executionIdRef.current, - error: errorMessage, - durationMs: data.duration || 0, - blockLogs: accumulatedBlockLogs, - isPreExecutionError: accumulatedBlockLogs.length === 0, - }) + await processSSEStream( + response.body.getReader(), + { + onExecutionStarted: (data) => { + logger.info('Execution started', { startTime: data.startTime }) + }, + + onBlockStarted: blockHandlers.onBlockStarted, + onBlockCompleted: blockHandlers.onBlockCompleted, + onBlockError: blockHandlers.onBlockError, + onBlockChildWorkflowStarted: blockHandlers.onBlockChildWorkflowStarted, + + onExecutionCompleted: (data) => { + setCurrentExecutionId(wfId, null) + executionResult = { + success: data.success, + output: data.output, + logs: accumulatedBlockLogs, + metadata: { + duration: data.duration, + startTime: data.startTime, + endTime: data.endTime, + }, + } + }, + + onExecutionCancelled: () => { + setCurrentExecutionId(wfId, null) + executionResult = { + success: false, + output: {}, + error: 'Execution was cancelled', + logs: accumulatedBlockLogs, + } + }, + + onExecutionError: (data) => { + setCurrentExecutionId(wfId, null) + const errorMessage = data.error || 'Execution failed' + executionResult = { + success: false, + output: {}, + error: errorMessage, + logs: accumulatedBlockLogs, + metadata: { duration: data.duration }, + } + + handleExecutionErrorConsole(addConsole, cancelRunningEntries, { + workflowId: wfId, + executionId: executionIdRef.current, + error: errorMessage, + durationMs: data.duration || 0, + blockLogs: accumulatedBlockLogs, + isPreExecutionError: accumulatedBlockLogs.length === 0, + }) + }, }, - }, 'CopilotExecution') + 'CopilotExecution' + ) } finally { setCurrentExecutionId(wfId, null) setActiveBlocks(wfId, new Set())