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 471cecd9b..f68d870d1 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 @@ -1661,11 +1661,16 @@ export function useWorkflowExecution() { }, onExecutionError: (data) => { - if (data.error?.includes('Block not found in workflow')) { + const isWorkflowModified = + data.error?.includes('Block not found in workflow') || + data.error?.includes('Upstream dependency not executed') + + if (isWorkflowModified) { clearLastExecutionSnapshot(workflowId) addNotification({ - level: 'info', - message: 'Workflow was modified. Run the workflow again to refresh.', + level: 'error', + message: + 'Workflow was modified. Run the workflow again to enable running from block.', workflowId, }) } else { diff --git a/apps/sim/executor/utils/run-from-block.test.ts b/apps/sim/executor/utils/run-from-block.test.ts index 07e39c58d..528b44def 100644 --- a/apps/sim/executor/utils/run-from-block.test.ts +++ b/apps/sim/executor/utils/run-from-block.test.ts @@ -331,6 +331,24 @@ describe('validateRunFromBlock', () => { expect(result.error).toContain('Upstream dependency not executed') }) + it('rejects blocks with unexecuted transitive upstream dependencies', () => { + // A → X → B → C, where X is new (not executed) + // Running from C should fail because X in upstream chain wasn't executed + const dag = createDAG([ + createNode('A', [{ target: 'X' }]), + createNode('X', [{ target: 'B' }]), + createNode('B', [{ target: 'C' }]), + createNode('C'), + ]) + const executedBlocks = new Set(['A', 'B', 'C']) // X was not executed (new block) + + const result = validateRunFromBlock('C', dag, executedBlocks) + + expect(result.valid).toBe(false) + expect(result.error).toContain('Upstream dependency not executed') + expect(result.error).toContain('X') + }) + it('allows blocks with no dependencies even if not previously executed', () => { // A and B are independent (no edges) const dag = createDAG([createNode('A'), createNode('B')]) diff --git a/apps/sim/executor/utils/run-from-block.ts b/apps/sim/executor/utils/run-from-block.ts index 5813d52b5..dbe2e3fcd 100644 --- a/apps/sim/executor/utils/run-from-block.ts +++ b/apps/sim/executor/utils/run-from-block.ts @@ -169,15 +169,15 @@ export function validateRunFromBlock( if (node.metadata.isSentinel) { return { valid: false, error: 'Cannot run from sentinel node' } } + } - if (node.incomingEdges.size > 0) { - for (const sourceId of node.incomingEdges.keys()) { - if (!executedBlocks.has(sourceId)) { - return { - valid: false, - error: `Upstream dependency not executed: ${sourceId}`, - } - } + // Check that ALL upstream blocks were executed (transitive check) + const { upstreamSet } = computeExecutionSets(dag, blockId) + for (const upstreamId of upstreamSet) { + if (!executedBlocks.has(upstreamId)) { + return { + valid: false, + error: `Upstream dependency not executed: ${upstreamId}`, } } }