Skip to content

Commit 0b16fa4

Browse files
fix(triggers): incoming edges should be filtered from execution and UI graph (#1777)
1 parent eac358b commit 0b16fa4

File tree

6 files changed

+88
-16
lines changed

6 files changed

+88
-16
lines changed

apps/sim/app/api/workflows/[id]/execute/route.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import {
2222
} from '@/lib/workflows/utils'
2323
import { validateWorkflowAccess } from '@/app/api/workflows/middleware'
2424
import { createErrorResponse, createSuccessResponse } from '@/app/api/workflows/utils'
25+
import { filterEdgesFromTriggerBlocks } from '@/app/workspace/[workspaceId]/w/[workflowId]/lib/workflow-execution-utils'
2526
import { Executor } from '@/executor'
2627
import type { ExecutionResult } from '@/executor/types'
2728
import { Serializer } from '@/serializer'
@@ -292,10 +293,13 @@ export async function executeWorkflow(
292293
logger.debug(`[${requestId}] No workflow variables found for: ${workflowId}`)
293294
}
294295

296+
// Filter out edges between trigger blocks - triggers are independent entry points
297+
const filteredEdges = filterEdgesFromTriggerBlocks(mergedStates, edges)
298+
295299
logger.debug(`[${requestId}] Serializing workflow: ${workflowId}`)
296300
const serializedWorkflow = new Serializer().serializeWorkflow(
297301
mergedStates,
298-
edges,
302+
filteredEdges,
299303
loops,
300304
parallels,
301305
true
@@ -335,7 +339,7 @@ export async function executeWorkflow(
335339
if (streamConfig?.enabled) {
336340
contextExtensions.stream = true
337341
contextExtensions.selectedOutputs = streamConfig.selectedOutputs || []
338-
contextExtensions.edges = edges.map((e: any) => ({
342+
contextExtensions.edges = filteredEdges.map((e: any) => ({
339343
source: e.source,
340344
target: e.target,
341345
}))

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

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { shallow } from 'zustand/shallow'
22
import { BlockPathCalculator } from '@/lib/block-path-calculator'
33
import { createLogger } from '@/lib/logs/console/logger'
44
import { getBlockOutputs } from '@/lib/workflows/block-outputs'
5+
import { TriggerUtils } from '@/lib/workflows/triggers'
56
import { useWorkflowRegistry } from '@/stores/workflows/registry/store'
67
import { useSubBlockStore } from '@/stores/workflows/subblock/store'
78
import { useWorkflowStore } from '@/stores/workflows/workflow/store'
@@ -110,8 +111,34 @@ export function useBlockConnections(blockId: string) {
110111
return merged
111112
}
112113

113-
// Find all blocks along paths leading to this block
114-
const allPathNodeIds = BlockPathCalculator.findAllPathNodes(edges, blockId)
114+
// Filter out edges between trigger blocks - triggers are independent entry points
115+
// This ensures UI tags only show blocks that are actually connected in execution
116+
const filteredEdges = edges.filter((edge) => {
117+
const sourceBlock = blocks[edge.source]
118+
const targetBlock = blocks[edge.target]
119+
120+
// If either block not found, keep the edge (might be in a different state structure)
121+
if (!sourceBlock || !targetBlock) {
122+
return true
123+
}
124+
125+
const sourceIsTrigger = TriggerUtils.isTriggerBlock({
126+
type: sourceBlock.type,
127+
triggerMode: sourceBlock.triggerMode,
128+
})
129+
130+
const targetIsTrigger = TriggerUtils.isTriggerBlock({
131+
type: targetBlock.type,
132+
triggerMode: targetBlock.triggerMode,
133+
})
134+
135+
// Filter out edges where source is trigger AND target is trigger
136+
// Keep edges from triggers to regular blocks
137+
return !(sourceIsTrigger && targetIsTrigger)
138+
})
139+
140+
// Find all blocks along paths leading to this block (using filtered edges)
141+
const allPathNodeIds = BlockPathCalculator.findAllPathNodes(filteredEdges, blockId)
115142

116143
// Map each path node to a ConnectedBlock structure
117144
const allPathConnections = allPathNodeIds
@@ -161,8 +188,8 @@ export function useBlockConnections(blockId: string) {
161188
})
162189
.filter(Boolean) as ConnectedBlock[]
163190

164-
// Keep the original incoming connections for compatibility
165-
const directIncomingConnections = edges
191+
// Keep the original incoming connections for compatibility (using filtered edges)
192+
const directIncomingConnections = filteredEdges
166193
.filter((edge) => edge.target === blockId)
167194
.map((edge) => {
168195
const sourceBlock = blocks[edge.source]

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import { useEnvironmentStore } from '@/stores/settings/environment/store'
1616
import { useWorkflowRegistry } from '@/stores/workflows/registry/store'
1717
import { mergeSubblockState } from '@/stores/workflows/utils'
1818
import { generateLoopBlocks, generateParallelBlocks } from '@/stores/workflows/workflow/utils'
19+
import { filterEdgesFromTriggerBlocks } from '../lib/workflow-execution-utils'
1920
import { useCurrentWorkflow } from './use-current-workflow'
2021

2122
const logger = createLogger('useWorkflowExecution')
@@ -773,8 +774,8 @@ export function useWorkflowExecution() {
773774
{} as Record<string, any>
774775
)
775776

776-
// Keep edges intact to allow execution starting from trigger blocks
777-
const filteredEdges = workflowEdges
777+
// Filter out edges between trigger blocks - triggers are independent entry points
778+
const filteredEdges = filterEdgesFromTriggerBlocks(filteredStates, workflowEdges)
778779

779780
// Derive subflows from the current filtered graph to avoid stale state
780781
const runtimeLoops = generateLoopBlocks(filteredStates)

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/lib/workflow-execution-utils.ts

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,11 @@
33
* This allows workflow execution with proper logging from both React hooks and tools
44
*/
55

6+
import type { Edge } from 'reactflow'
67
import { v4 as uuidv4 } from 'uuid'
78
import { createLogger } from '@/lib/logs/console/logger'
89
import { buildTraceSpans } from '@/lib/logs/execution/trace-spans/trace-spans'
10+
import { TriggerUtils } from '@/lib/workflows/triggers'
911
import type { BlockOutput } from '@/blocks/types'
1012
import { Executor } from '@/executor'
1113
import type { ExecutionResult, StreamingExecution } from '@/executor/types'
@@ -81,6 +83,37 @@ export function getWorkflowExecutionContext(): WorkflowExecutionContext {
8183
}
8284
}
8385

86+
/**
87+
* Filter out edges between trigger blocks.
88+
* Trigger blocks are independent entry points and should not have edges to other trigger blocks.
89+
* However, trigger blocks can have edges to regular blocks.
90+
*/
91+
export function filterEdgesFromTriggerBlocks(blocks: Record<string, any>, edges: Edge[]): Edge[] {
92+
return edges.filter((edge) => {
93+
const sourceBlock = blocks[edge.source]
94+
const targetBlock = blocks[edge.target]
95+
96+
// If either block not found, keep the edge (might be in a different state structure)
97+
if (!sourceBlock || !targetBlock) {
98+
return true
99+
}
100+
101+
const sourceIsTrigger = TriggerUtils.isTriggerBlock({
102+
type: sourceBlock.type,
103+
triggerMode: sourceBlock.triggerMode,
104+
})
105+
106+
const targetIsTrigger = TriggerUtils.isTriggerBlock({
107+
type: targetBlock.type,
108+
triggerMode: targetBlock.triggerMode,
109+
})
110+
111+
// Filter out edges where source is trigger AND target is trigger
112+
// Keep edges from triggers to regular blocks
113+
return !(sourceIsTrigger && targetIsTrigger)
114+
})
115+
}
116+
84117
/**
85118
* Execute a workflow with proper state management and logging
86119
* This is the core execution logic extracted from useWorkflowExecution
@@ -168,9 +201,9 @@ export async function executeWorkflowWithLogging(
168201
{} as Record<string, any>
169202
)
170203

171-
// Don't filter edges - let all connections remain intact
172-
// The executor's routing system will handle execution paths properly
173-
const filteredEdges = workflowEdges
204+
// Filter out edges from trigger blocks - triggers are independent entry points
205+
// and should not have edges to other trigger blocks
206+
const filteredEdges = filterEdgesFromTriggerBlocks(validBlocks, workflowEdges)
174207

175208
// Create serialized workflow with filtered blocks and edges
176209
const workflow = new Serializer().serializeWorkflow(

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

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import { WorkflowBlock } from '@/app/workspace/[workspaceId]/w/[workflowId]/comp
3232
import { WorkflowEdge } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-edge/workflow-edge'
3333
import { CollaboratorCursorLayer } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-presence/collaborator-cursor-layer'
3434
import { useCurrentWorkflow } from '@/app/workspace/[workspaceId]/w/[workflowId]/hooks'
35+
import { filterEdgesFromTriggerBlocks } from '@/app/workspace/[workspaceId]/w/[workflowId]/lib/workflow-execution-utils'
3536
import {
3637
getNodeAbsolutePosition,
3738
getNodeDepth,
@@ -151,8 +152,10 @@ const WorkflowContent = React.memo(() => {
151152
// Get diff analysis for edge reconstruction
152153
const { diffAnalysis, isShowingDiff, isDiffReady } = useWorkflowDiffStore()
153154

154-
// Reconstruct deleted edges when viewing original workflow
155+
// Reconstruct deleted edges when viewing original workflow and filter trigger edges
155156
const edgesForDisplay = useMemo(() => {
157+
let edgesToFilter = edges
158+
156159
// If we're not in diff mode and we have diff analysis with deleted edges,
157160
// we need to reconstruct those deleted edges and add them to the display
158161
// Only do this if diff is ready to prevent race conditions
@@ -214,11 +217,11 @@ const WorkflowContent = React.memo(() => {
214217
})
215218

216219
// Combine existing edges with reconstructed deleted edges
217-
return [...edges, ...reconstructedEdges]
220+
edgesToFilter = [...edges, ...reconstructedEdges]
218221
}
219222

220-
// Otherwise, just use the edges as-is
221-
return edges
223+
// Filter out edges between trigger blocks for consistent UI and execution behavior
224+
return filterEdgesFromTriggerBlocks(blocks, edgesToFilter)
222225
}, [edges, isShowingDiff, isDiffReady, diffAnalysis, blocks])
223226

224227
// User permissions - get current user's specific permissions from context

apps/sim/background/workflow-execution.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { buildTraceSpans } from '@/lib/logs/execution/trace-spans/trace-spans'
1111
import { decryptSecret } from '@/lib/utils'
1212
import { loadDeployedWorkflowState } from '@/lib/workflows/db-helpers'
1313
import { updateWorkflowRunCounts } from '@/lib/workflows/utils'
14+
import { filterEdgesFromTriggerBlocks } from '@/app/workspace/[workspaceId]/w/[workflowId]/lib/workflow-execution-utils'
1415
import { Executor } from '@/executor'
1516
import { Serializer } from '@/serializer'
1617
import { mergeSubblockState } from '@/stores/workflows/server-utils'
@@ -107,11 +108,14 @@ export async function executeWorkflowJob(payload: WorkflowExecutionPayload) {
107108
variables: decryptedEnvVars,
108109
})
109110

111+
// Filter out edges between trigger blocks - triggers are independent entry points
112+
const filteredEdges = filterEdgesFromTriggerBlocks(mergedStates, edges)
113+
110114
// Create serialized workflow
111115
const serializer = new Serializer()
112116
const serializedWorkflow = serializer.serializeWorkflow(
113117
mergedStates,
114-
edges,
118+
filteredEdges,
115119
loops || {},
116120
parallels || {},
117121
true // Enable validation during execution

0 commit comments

Comments
 (0)