Skip to content

Commit a4b4971

Browse files
committed
fix(execution): scope execution state per workflow to prevent cross-workflow bleed
1 parent 73540e3 commit a4b4971

File tree

16 files changed

+910
-166
lines changed

16 files changed

+910
-166
lines changed

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/action-bar/action-bar.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { useUserPermissionsContext } from '@/app/workspace/[workspaceId]/provide
77
import { useWorkflowExecution } from '@/app/workspace/[workspaceId]/w/[workflowId]/hooks'
88
import { validateTriggerPaste } from '@/app/workspace/[workspaceId]/w/[workflowId]/utils'
99
import { useCollaborativeWorkflow } from '@/hooks/use-collaborative-workflow'
10-
import { useExecutionStore } from '@/stores/execution'
10+
import { useCurrentWorkflowExecution, useExecutionStore } from '@/stores/execution'
1111
import { useNotificationStore } from '@/stores/notifications'
1212
import { useWorkflowRegistry } from '@/stores/workflows/registry/store'
1313
import { useWorkflowStore } from '@/stores/workflows/workflow/store'
@@ -114,7 +114,8 @@ export const ActionBar = memo(
114114
)
115115

116116
const { activeWorkflowId } = useWorkflowRegistry()
117-
const { isExecuting, getLastExecutionSnapshot } = useExecutionStore()
117+
const { isExecuting } = useCurrentWorkflowExecution()
118+
const { getLastExecutionSnapshot } = useExecutionStore()
118119
const userPermissions = useUserPermissionsContext()
119120
const edges = useWorkflowStore((state) => state.edges)
120121

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/chat/chat.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ import { useWorkflowExecution } from '@/app/workspace/[workspaceId]/w/[workflowI
5151
import type { BlockLog, ExecutionResult } from '@/executor/types'
5252
import { useChatStore } from '@/stores/chat/store'
5353
import { getChatPosition } from '@/stores/chat/utils'
54-
import { useExecutionStore } from '@/stores/execution'
54+
import { useCurrentWorkflowExecution } from '@/stores/execution'
5555
import { useOperationQueue } from '@/stores/operation-queue/store'
5656
import { useTerminalConsoleStore } from '@/stores/terminal'
5757
import { useWorkflowRegistry } from '@/stores/workflows/registry/store'
@@ -256,7 +256,7 @@ export function Chat() {
256256
const hasConsoleHydrated = useTerminalConsoleStore((state) => state._hasHydrated)
257257
const entriesFromStore = useTerminalConsoleStore((state) => state.entries)
258258
const entries = hasConsoleHydrated ? entriesFromStore : []
259-
const { isExecuting } = useExecutionStore()
259+
const { isExecuting } = useCurrentWorkflowExecution()
260260
const { handleRunWorkflow, handleCancelExecution } = useWorkflowExecution()
261261
const { data: session } = useSession()
262262
const { addToQueue } = useOperationQueue()

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/hooks/use-block-state.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { useCallback } from 'react'
22
import type { DiffStatus } from '@/lib/workflows/diff/types'
33
import { hasDiffStatus } from '@/lib/workflows/diff/types'
4-
import { useExecutionStore } from '@/stores/execution'
4+
import { useIsBlockActive } from '@/stores/execution'
55
import { useWorkflowDiffStore } from '@/stores/workflow-diff'
66
import type { CurrentWorkflow } from '../../../hooks/use-current-workflow'
77
import type { WorkflowBlockProps } from '../types'
@@ -67,7 +67,7 @@ export function useBlockState(
6767
const isDeletedBlock = !isShowingDiff && diffAnalysis?.deleted_blocks?.includes(blockId)
6868

6969
// Execution state
70-
const isActiveBlock = useExecutionStore((state) => state.activeBlockIds.has(blockId))
70+
const isActiveBlock = useIsBlockActive(blockId)
7171
const isActive = data.isActive || isActiveBlock
7272

7373
return {

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-edge/workflow-edge.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { X } from 'lucide-react'
33
import { BaseEdge, EdgeLabelRenderer, type EdgeProps, getSmoothStepPath } from 'reactflow'
44
import { useShallow } from 'zustand/react/shallow'
55
import type { EdgeDiffStatus } from '@/lib/workflows/diff/types'
6-
import { useExecutionStore } from '@/stores/execution'
6+
import { useLastRunEdges } from '@/stores/execution'
77
import { useWorkflowDiffStore } from '@/stores/workflow-diff'
88

99
/** Extended edge props with optional handle identifiers */
@@ -49,7 +49,7 @@ const WorkflowEdgeComponent = ({
4949
isDiffReady: state.isDiffReady,
5050
}))
5151
)
52-
const lastRunEdges = useExecutionStore((state) => state.lastRunEdges)
52+
const lastRunEdges = useLastRunEdges()
5353

5454
const dataSourceHandle = (data as { sourceHandle?: string } | undefined)?.sourceHandle
5555
const isErrorEdge = (sourceHandle ?? dataSourceHandle) === 'error'

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/hooks/use-block-visual.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { useBlockState } from '@/app/workspace/[workspaceId]/w/[workflowId]/comp
33
import type { WorkflowBlockProps } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/types'
44
import { useCurrentWorkflow } from '@/app/workspace/[workspaceId]/w/[workflowId]/hooks/use-current-workflow'
55
import { getBlockRingStyles } from '@/app/workspace/[workspaceId]/w/[workflowId]/utils/block-ring-utils'
6-
import { useExecutionStore } from '@/stores/execution'
6+
import { useLastRunPath } from '@/stores/execution'
77
import { usePanelEditorStore, usePanelStore } from '@/stores/panel'
88
import { useWorkflowRegistry } from '@/stores/workflows/registry/store'
99

@@ -64,7 +64,7 @@ export function useBlockVisual({
6464
)
6565
const isEditorOpen = !isPreview && isThisBlockInEditor && activeTabIsEditor
6666

67-
const lastRunPath = useExecutionStore((state) => state.lastRunPath)
67+
const lastRunPath = useLastRunPath()
6868
const runPathStatus = isPreview ? undefined : lastRunPath.get(blockId)
6969

7070
const setCurrentBlockId = usePanelEditorStore((state) => state.setCurrentBlockId)

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/hooks/use-workflow-execution.ts

Lines changed: 58 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ import { coerceValue } from '@/executor/utils/start-block'
3434
import { subscriptionKeys } from '@/hooks/queries/subscription'
3535
import { useExecutionStream } from '@/hooks/use-execution-stream'
3636
import { WorkflowValidationError } from '@/serializer'
37-
import { useExecutionStore } from '@/stores/execution'
37+
import { useCurrentWorkflowExecution, useExecutionStore } from '@/stores/execution'
3838
import { useNotificationStore } from '@/stores/notifications'
3939
import { useVariablesStore } from '@/stores/panel'
4040
import { useEnvironmentStore } from '@/stores/settings/environment'
@@ -112,12 +112,9 @@ export function useWorkflowExecution() {
112112
useTerminalConsoleStore()
113113
const { getAllVariables } = useEnvironmentStore()
114114
const { getVariablesByWorkflowId, variables } = useVariablesStore()
115+
const { isExecuting, isDebugging, pendingBlocks, executor, debugContext } =
116+
useCurrentWorkflowExecution()
115117
const {
116-
isExecuting,
117-
isDebugging,
118-
pendingBlocks,
119-
executor,
120-
debugContext,
121118
setIsExecuting,
122119
setIsDebugging,
123120
setPendingBlocks,
@@ -158,13 +155,15 @@ export function useWorkflowExecution() {
158155
* Resets all debug-related state
159156
*/
160157
const resetDebugState = useCallback(() => {
161-
setIsExecuting(false)
162-
setIsDebugging(false)
163-
setDebugContext(null)
164-
setExecutor(null)
165-
setPendingBlocks([])
166-
setActiveBlocks(new Set())
158+
if (!activeWorkflowId) return
159+
setIsExecuting(activeWorkflowId, false)
160+
setIsDebugging(activeWorkflowId, false)
161+
setDebugContext(activeWorkflowId, null)
162+
setExecutor(activeWorkflowId, null)
163+
setPendingBlocks(activeWorkflowId, [])
164+
setActiveBlocks(activeWorkflowId, new Set())
167165
}, [
166+
activeWorkflowId,
168167
setIsExecuting,
169168
setIsDebugging,
170169
setDebugContext,
@@ -312,18 +311,20 @@ export function useWorkflowExecution() {
312311
} = config
313312

314313
const updateActiveBlocks = (blockId: string, isActive: boolean) => {
314+
if (!workflowId) return
315315
if (isActive) {
316316
activeBlocksSet.add(blockId)
317317
} else {
318318
activeBlocksSet.delete(blockId)
319319
}
320-
setActiveBlocks(new Set(activeBlocksSet))
320+
setActiveBlocks(workflowId, new Set(activeBlocksSet))
321321
}
322322

323323
const markIncomingEdges = (blockId: string) => {
324+
if (!workflowId) return
324325
const incomingEdges = workflowEdges.filter((edge) => edge.target === blockId)
325326
incomingEdges.forEach((edge) => {
326-
setEdgeRunStatus(edge.id, 'success')
327+
setEdgeRunStatus(workflowId, edge.id, 'success')
327328
})
328329
}
329330

@@ -459,7 +460,7 @@ export function useWorkflowExecution() {
459460

460461
const onBlockCompleted = (data: BlockCompletedData) => {
461462
updateActiveBlocks(data.blockId, false)
462-
setBlockRunStatus(data.blockId, 'success')
463+
if (workflowId) setBlockRunStatus(workflowId, data.blockId, 'success')
463464

464465
executedBlockIds.add(data.blockId)
465466
accumulatedBlockStates.set(data.blockId, {
@@ -489,7 +490,7 @@ export function useWorkflowExecution() {
489490

490491
const onBlockError = (data: BlockErrorData) => {
491492
updateActiveBlocks(data.blockId, false)
492-
setBlockRunStatus(data.blockId, 'error')
493+
if (workflowId) setBlockRunStatus(workflowId, data.blockId, 'error')
493494

494495
executedBlockIds.add(data.blockId)
495496
accumulatedBlockStates.set(data.blockId, {
@@ -547,19 +548,20 @@ export function useWorkflowExecution() {
547548
*/
548549
const handleDebugSessionContinuation = useCallback(
549550
(result: ExecutionResult) => {
551+
if (!activeWorkflowId) return
550552
logger.info('Debug step completed, next blocks pending', {
551553
nextPendingBlocks: result.metadata?.pendingBlocks?.length || 0,
552554
})
553555

554556
// Update debug context and pending blocks
555557
if (result.metadata?.context) {
556-
setDebugContext(result.metadata.context)
558+
setDebugContext(activeWorkflowId, result.metadata.context)
557559
}
558560
if (result.metadata?.pendingBlocks) {
559-
setPendingBlocks(result.metadata.pendingBlocks)
561+
setPendingBlocks(activeWorkflowId, result.metadata.pendingBlocks)
560562
}
561563
},
562-
[setDebugContext, setPendingBlocks]
564+
[activeWorkflowId, setDebugContext, setPendingBlocks]
563565
)
564566

565567
/**
@@ -663,11 +665,11 @@ export function useWorkflowExecution() {
663665

664666
// Reset execution result and set execution state
665667
setExecutionResult(null)
666-
setIsExecuting(true)
668+
setIsExecuting(activeWorkflowId, true)
667669

668670
// Set debug mode only if explicitly requested
669671
if (enableDebug) {
670-
setIsDebugging(true)
672+
setIsDebugging(activeWorkflowId, true)
671673
}
672674

673675
// Determine if this is a chat execution
@@ -965,9 +967,9 @@ export function useWorkflowExecution() {
965967
controller.close()
966968
}
967969
if (currentChatExecutionIdRef.current === executionId) {
968-
setIsExecuting(false)
969-
setIsDebugging(false)
970-
setActiveBlocks(new Set())
970+
setIsExecuting(activeWorkflowId, false)
971+
setIsDebugging(activeWorkflowId, false)
972+
setActiveBlocks(activeWorkflowId, new Set())
971973
}
972974
}
973975
},
@@ -989,16 +991,16 @@ export function useWorkflowExecution() {
989991
'manual'
990992
)
991993
if (result && 'metadata' in result && result.metadata?.isDebugSession) {
992-
setDebugContext(result.metadata.context || null)
994+
setDebugContext(activeWorkflowId, result.metadata.context || null)
993995
if (result.metadata.pendingBlocks) {
994-
setPendingBlocks(result.metadata.pendingBlocks)
996+
setPendingBlocks(activeWorkflowId, result.metadata.pendingBlocks)
995997
}
996998
} else if (result && 'success' in result) {
997999
setExecutionResult(result)
9981000
// Reset execution state after successful non-debug execution
999-
setIsExecuting(false)
1000-
setIsDebugging(false)
1001-
setActiveBlocks(new Set())
1001+
setIsExecuting(activeWorkflowId, false)
1002+
setIsDebugging(activeWorkflowId, false)
1003+
setActiveBlocks(activeWorkflowId, new Set())
10021004

10031005
if (isChatExecution) {
10041006
if (!result.metadata) {
@@ -1179,7 +1181,7 @@ export function useWorkflowExecution() {
11791181
logger.error('No trigger blocks found for manual run', {
11801182
allBlockTypes: Object.values(filteredStates).map((b) => b.type),
11811183
})
1182-
setIsExecuting(false)
1184+
if (activeWorkflowId) setIsExecuting(activeWorkflowId, false)
11831185
throw error
11841186
}
11851187

@@ -1195,7 +1197,7 @@ export function useWorkflowExecution() {
11951197
'Workflow Validation'
11961198
)
11971199
logger.error('Multiple API triggers found')
1198-
setIsExecuting(false)
1200+
if (activeWorkflowId) setIsExecuting(activeWorkflowId, false)
11991201
throw error
12001202
}
12011203

@@ -1220,7 +1222,7 @@ export function useWorkflowExecution() {
12201222
'Workflow Validation'
12211223
)
12221224
logger.error('Trigger has no outgoing connections', { triggerName, startBlockId })
1223-
setIsExecuting(false)
1225+
if (activeWorkflowId) setIsExecuting(activeWorkflowId, false)
12241226
throw error
12251227
}
12261228
}
@@ -1251,7 +1253,7 @@ export function useWorkflowExecution() {
12511253
'Workflow Validation'
12521254
)
12531255
logger.error('No startBlockId found after trigger search')
1254-
setIsExecuting(false)
1256+
if (activeWorkflowId) setIsExecuting(activeWorkflowId, false)
12551257
throw error
12561258
}
12571259

@@ -1457,8 +1459,10 @@ export function useWorkflowExecution() {
14571459
logger.info('Execution aborted by user')
14581460

14591461
// Reset execution state
1460-
setIsExecuting(false)
1461-
setActiveBlocks(new Set())
1462+
if (activeWorkflowId) {
1463+
setIsExecuting(activeWorkflowId, false)
1464+
setActiveBlocks(activeWorkflowId, new Set())
1465+
}
14621466

14631467
// Return gracefully without error
14641468
return {
@@ -1533,9 +1537,11 @@ export function useWorkflowExecution() {
15331537
}
15341538

15351539
setExecutionResult(errorResult)
1536-
setIsExecuting(false)
1537-
setIsDebugging(false)
1538-
setActiveBlocks(new Set())
1540+
if (activeWorkflowId) {
1541+
setIsExecuting(activeWorkflowId, false)
1542+
setIsDebugging(activeWorkflowId, false)
1543+
setActiveBlocks(activeWorkflowId, new Set())
1544+
}
15391545

15401546
let notificationMessage = WORKFLOW_EXECUTION_FAILURE_MESSAGE
15411547
if (isRecord(error) && isRecord(error.request) && sanitizeMessage(error.request.url)) {
@@ -1706,21 +1712,21 @@ export function useWorkflowExecution() {
17061712
const handleCancelExecution = useCallback(() => {
17071713
logger.info('Workflow execution cancellation requested')
17081714

1709-
// Cancel the execution stream (server-side)
1710-
executionStream.cancel()
1715+
// Cancel the execution stream for this workflow (server-side)
1716+
executionStream.cancel(activeWorkflowId ?? undefined)
17111717

17121718
// Mark current chat execution as superseded so its cleanup won't affect new executions
17131719
currentChatExecutionIdRef.current = null
17141720

17151721
// Mark all running entries as canceled in the terminal
17161722
if (activeWorkflowId) {
17171723
cancelRunningEntries(activeWorkflowId)
1718-
}
17191724

1720-
// Reset execution state - this triggers chat stream cleanup via useEffect in chat.tsx
1721-
setIsExecuting(false)
1722-
setIsDebugging(false)
1723-
setActiveBlocks(new Set())
1725+
// Reset execution state - this triggers chat stream cleanup via useEffect in chat.tsx
1726+
setIsExecuting(activeWorkflowId, false)
1727+
setIsDebugging(activeWorkflowId, false)
1728+
setActiveBlocks(activeWorkflowId, new Set())
1729+
}
17241730

17251731
// If in debug mode, also reset debug state
17261732
if (isDebugging) {
@@ -1833,7 +1839,7 @@ export function useWorkflowExecution() {
18331839
}
18341840
}
18351841

1836-
setIsExecuting(true)
1842+
setIsExecuting(workflowId, true)
18371843
const executionId = uuidv4()
18381844
const accumulatedBlockLogs: BlockLog[] = []
18391845
const accumulatedBlockStates = new Map<string, BlockState>()
@@ -1929,8 +1935,8 @@ export function useWorkflowExecution() {
19291935
logger.error('Run-from-block failed:', error)
19301936
}
19311937
} finally {
1932-
setIsExecuting(false)
1933-
setActiveBlocks(new Set())
1938+
setIsExecuting(workflowId, false)
1939+
setActiveBlocks(workflowId, new Set())
19341940
}
19351941
},
19361942
[
@@ -1962,7 +1968,7 @@ export function useWorkflowExecution() {
19621968
logger.info('Starting run-until-block execution', { workflowId, stopAfterBlockId: blockId })
19631969

19641970
setExecutionResult(null)
1965-
setIsExecuting(true)
1971+
setIsExecuting(activeWorkflowId!, true)
19661972

19671973
const executionId = uuidv4()
19681974
try {
@@ -1981,9 +1987,9 @@ export function useWorkflowExecution() {
19811987
const errorResult = handleExecutionError(error, { executionId })
19821988
return errorResult
19831989
} finally {
1984-
setIsExecuting(false)
1985-
setIsDebugging(false)
1986-
setActiveBlocks(new Set())
1990+
setIsExecuting(activeWorkflowId!, false)
1991+
setIsDebugging(activeWorkflowId!, false)
1992+
setActiveBlocks(activeWorkflowId!, new Set())
19871993
}
19881994
},
19891995
[activeWorkflowId, setExecutionResult, setIsExecuting, setIsDebugging, setActiveBlocks]

0 commit comments

Comments
 (0)