diff --git a/apps/sim/app/api/workflows/[id]/execute/route.ts b/apps/sim/app/api/workflows/[id]/execute/route.ts index 9665f881f0..8f2c9934ce 100644 --- a/apps/sim/app/api/workflows/[id]/execute/route.ts +++ b/apps/sim/app/api/workflows/[id]/execute/route.ts @@ -510,7 +510,10 @@ export async function POST(req: NextRequest, { params }: { params: Promise<{ id: return NextResponse.json(filteredResult) } catch (error: any) { - logger.error(`[${requestId}] Non-SSE execution failed:`, error) + // Block errors are already logged with full details by BlockExecutor + // Only log the error message here to avoid duplicate logging + const errorMessage = error.message || 'Unknown error' + logger.error(`[${requestId}] Non-SSE execution failed: ${errorMessage}`) const executionResult = error.executionResult @@ -803,7 +806,10 @@ export async function POST(req: NextRequest, { params }: { params: Promise<{ id: }, }) } catch (error: any) { - logger.error(`[${requestId}] SSE execution failed:`, error) + // Block errors are already logged with full details by BlockExecutor + // Only log the error message here to avoid duplicate logging + const errorMessage = error.message || 'Unknown error' + logger.error(`[${requestId}] SSE execution failed: ${errorMessage}`) const executionResult = error.executionResult @@ -813,7 +819,7 @@ export async function POST(req: NextRequest, { params }: { params: Promise<{ id: executionId, workflowId, data: { - error: executionResult?.error || error.message || 'Unknown error', + error: executionResult?.error || errorMessage, duration: executionResult?.metadata?.duration || 0, }, }) diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel-new/panel-new.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel-new/panel-new.tsx index dbec356b3f..5b8ec067ad 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel-new/panel-new.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel-new/panel-new.tsx @@ -244,16 +244,11 @@ export function Panel() { shortcut: 'Mod+Enter', allowInEditable: false, handler: () => { - try { - if (isExecuting) { - cancelWorkflow() - } else if (!isButtonDisabled) { - runWorkflow() - } else { - logger.warn('Cannot run workflow: button is disabled') - } - } catch (err) { - logger.error('Failed to execute Cmd+Enter command', { err }) + // Do exactly what the Run button does + if (isExecuting) { + cancelWorkflow() + } else { + runWorkflow() } }, }, 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 671a0fc109..ba78faa2d9 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,7 +16,7 @@ import { WorkflowValidationError } from '@/serializer' import { useExecutionStore } from '@/stores/execution/store' import { useVariablesStore } from '@/stores/panel/variables/store' import { useEnvironmentStore } from '@/stores/settings/environment/store' -import { useTerminalConsoleStore } from '@/stores/terminal' +import { type ConsoleEntry, useTerminalConsoleStore } from '@/stores/terminal' import { useWorkflowDiffStore } from '@/stores/workflow-diff' import { useWorkflowRegistry } from '@/stores/workflows/registry/store' import { mergeSubblockState } from '@/stores/workflows/utils' @@ -1005,21 +1005,31 @@ export function useWorkflowExecution() { logs: [], } - // Add error to console - addConsole({ - input: {}, - output: {}, - success: false, - error: data.error, - durationMs: data.duration || 0, - startedAt: new Date(Date.now() - (data.duration || 0)).toISOString(), - endedAt: new Date().toISOString(), - workflowId: activeWorkflowId, - blockId: 'workflow', - executionId: executionId || uuidv4(), - blockName: 'Workflow Execution', - blockType: 'workflow', - }) + // Only add workflow-level error if no blocks have executed yet + // This catches pre-execution errors (validation, serialization, etc.) + // Block execution errors are already logged via onBlockError callback + const { entries } = useTerminalConsoleStore.getState() + const existingLogs = entries.filter( + (log: ConsoleEntry) => log.executionId === executionId + ) + + if (existingLogs.length === 0) { + // No blocks executed yet - this is a pre-execution error + addConsole({ + input: {}, + output: {}, + success: false, + error: data.error, + durationMs: data.duration || 0, + startedAt: new Date(Date.now() - (data.duration || 0)).toISOString(), + endedAt: new Date().toISOString(), + workflowId: activeWorkflowId, + blockId: 'validation', + executionId: executionId || uuidv4(), + blockName: 'Workflow Validation', + blockType: 'validation', + }) + } }, }, }) diff --git a/apps/sim/executor/execution/engine.ts b/apps/sim/executor/execution/engine.ts index b29297e158..9deab28cc1 100644 --- a/apps/sim/executor/execution/engine.ts +++ b/apps/sim/executor/execution/engine.ts @@ -72,9 +72,13 @@ export class ExecutionEngine { logs: this.context.blockLogs, metadata: this.context.metadata, } - const executionError = new Error(errorMessage) - ;(executionError as any).executionResult = executionResult - throw executionError + + // Attach executionResult to the original error instead of creating a new one + // This preserves block error metadata (blockId, blockName, blockType, etc.) + if (error && typeof error === 'object') { + ;(error as any).executionResult = executionResult + } + throw error } } @@ -105,6 +109,11 @@ export class ExecutionEngine { private trackExecution(promise: Promise): void { this.executing.add(promise) + // Attach error handler to prevent unhandled rejection warnings + // The actual error handling happens in waitForAllExecutions/waitForAnyExecution + promise.catch(() => { + // Error will be properly handled by Promise.all/Promise.race in wait methods + }) promise.finally(() => { this.executing.delete(promise) })