Skip to content

Commit f88df6f

Browse files
committed
fix(executor): a skipped stop block fails a run another branch paused, and names a Response ending
- A run whose stop block was proven unreachable fails even when another branch paused, instead of returning a paused run that would resume past it. - When a Response block ended the run first, the error says so rather than claiming no path leads to the stop block.
1 parent 7f3d93c commit f88df6f

2 files changed

Lines changed: 57 additions & 7 deletions

File tree

‎apps/sim/executor/execution/engine.test.ts‎

Lines changed: 45 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1117,7 +1117,10 @@ describe('ExecutionEngine', () => {
11171117
handleNodeCompletion: vi.fn(),
11181118
} as unknown as NodeExecutionOrchestrator
11191119
const engine = new ExecutionEngine(
1120-
createMockContext({ stopAfterBlockId }),
1120+
createMockContext({
1121+
stopAfterBlockId,
1122+
decisions: { router: new Map(), condition: new Map() },
1123+
}),
11211124
dag,
11221125
new EdgeManager(dag),
11231126
nodeOrchestrator
@@ -1184,6 +1187,46 @@ describe('ExecutionEngine', () => {
11841187
expect(executed).toEqual(['start', 'router'])
11851188
})
11861189

1190+
it('fails rather than pausing when another branch pauses after the stop block is skipped', async () => {
1191+
const { engine, executed } = buildRun(
1192+
{
1193+
version: '1',
1194+
blocks: [
1195+
block('start', BlockType.STARTER),
1196+
block('approval'),
1197+
block('condition', BlockType.CONDITION),
1198+
block('taken'),
1199+
block('stop'),
1200+
],
1201+
connections: [
1202+
{ source: 'start', target: 'approval' },
1203+
{ source: 'start', target: 'condition' },
1204+
{ source: 'condition', target: 'taken', sourceHandle: 'condition-if' },
1205+
{ source: 'condition', target: 'stop', sourceHandle: 'condition-else' },
1206+
],
1207+
loops: {},
1208+
parallels: {},
1209+
},
1210+
'stop',
1211+
{
1212+
approval: {
1213+
response: { status: 'paused' },
1214+
_pauseMetadata: {
1215+
contextId: 'pause-1',
1216+
blockId: 'approval',
1217+
response: { status: 'paused' },
1218+
timestamp: new Date().toISOString(),
1219+
pauseKind: 'hitl',
1220+
},
1221+
},
1222+
condition: { selectedOption: 'if' },
1223+
}
1224+
)
1225+
1226+
await expect(engine.run('start')).rejects.toThrow('Stop block "stop" (stop) was not reached')
1227+
expect(executed).toEqual(['start', 'approval', 'condition'])
1228+
})
1229+
11871230
it('fails when the stop block sits on an error path the run never takes', async () => {
11881231
const { engine, executed } = buildRun(
11891232
{
@@ -1332,7 +1375,7 @@ describe('ExecutionEngine', () => {
13321375

13331376
const pastResponse = buildRun(workflow, 'stop')
13341377
await expect(pastResponse.engine.run('start')).rejects.toThrow(
1335-
'Stop block "stop" (stop) was not reached'
1378+
'Stop block "stop" (stop) was not reached: a Response block ended the run first'
13361379
)
13371380
expect(pastResponse.executed).toEqual(['start', 'respond'])
13381381
})

‎apps/sim/executor/execution/engine.ts‎

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ export class ExecutionEngine {
3737
private stoppedEarlyFlag = false
3838
private stopBlockQueued = false
3939
private stopBlockReached = false
40+
private stopBlockUnreachable = false
4041
private executionError: Error | null = null
4142
private abortPromise!: Promise<void>
4243
private abortResolve!: () => void
@@ -127,12 +128,13 @@ export class ExecutionEngine {
127128
throw this.executionError
128129
}
129130

130-
if (this.pausedBlocks.size > 0) {
131-
return this.buildPausedResult(startTime)
131+
/** A pause keeps a run whose stop block can still run; one proven unreachable fails. */
132+
if (!this.cancelledFlag && (this.stopBlockUnreachable || this.pausedBlocks.size === 0)) {
133+
this.assertStopBlockReached()
132134
}
133135

134-
if (!this.cancelledFlag) {
135-
this.assertStopBlockReached()
136+
if (this.pausedBlocks.size > 0) {
137+
return this.buildPausedResult(startTime)
136138
}
137139

138140
const endTime = performance.now()
@@ -546,6 +548,7 @@ export class ExecutionEngine {
546548
return
547549
}
548550
this.execLogger.info('Stopping execution: the stop block can no longer run', { stopBlockId })
551+
this.stopBlockUnreachable = true
549552
this.stoppedEarlyFlag = true
550553
}
551554

@@ -562,7 +565,11 @@ export class ExecutionEngine {
562565
: node?.block.metadata?.name
563566
? `"${node.block.metadata.name}" (${stopBlockId})`
564567
: stopBlockId
565-
throw new Error(`Stop block ${label} was not reached: no path this run took leads to it`)
568+
const reason =
569+
this.responseOutputLocked && !this.stopBlockUnreachable
570+
? 'a Response block ended the run first'
571+
: 'no path this run took leads to it'
572+
throw new Error(`Stop block ${label} was not reached: ${reason}`)
566573
}
567574

568575
private setFinalOutput(nodeId: string, output: NormalizedBlockOutput): void {

0 commit comments

Comments
 (0)