Skip to content

Commit 6780e00

Browse files
committed
fix(hitl): emit execution:cancelled event to canvas when cancelling paused execution
Paused HITL executions have no active SSE stream, so the canvas never received the cancellation event. Now writes execution:cancelled to the event buffer and updates the stream meta so the canvas reconnect path picks it up and shows 'Execution Cancelled'.
1 parent 85d9fd6 commit 6780e00

File tree

2 files changed

+29
-0
lines changed

2 files changed

+29
-0
lines changed

apps/sim/app/api/workflows/[id]/executions/[executionId]/cancel/route.test.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,18 @@ const {
1111
mockMarkExecutionCancelled,
1212
mockAbortManualExecution,
1313
mockCancelPausedExecution,
14+
mockSetExecutionMeta,
15+
mockWriteEvent,
16+
mockCloseWriter,
1417
} = vi.hoisted(() => ({
1518
mockCheckHybridAuth: vi.fn(),
1619
mockAuthorizeWorkflowByWorkspacePermission: vi.fn(),
1720
mockMarkExecutionCancelled: vi.fn(),
1821
mockAbortManualExecution: vi.fn(),
1922
mockCancelPausedExecution: vi.fn(),
23+
mockSetExecutionMeta: vi.fn(),
24+
mockWriteEvent: vi.fn(),
25+
mockCloseWriter: vi.fn(),
2026
}))
2127

2228
vi.mock('@/lib/auth/hybrid', () => ({
@@ -46,6 +52,14 @@ vi.mock('@/lib/posthog/server', () => ({
4652
captureServerEvent: vi.fn(),
4753
}))
4854

55+
vi.mock('@/lib/execution/event-buffer', () => ({
56+
setExecutionMeta: (...args: unknown[]) => mockSetExecutionMeta(...args),
57+
createExecutionEventWriter: () => ({
58+
write: (...args: unknown[]) => mockWriteEvent(...args),
59+
close: () => mockCloseWriter(),
60+
}),
61+
}))
62+
4963
import { POST } from './route'
5064

5165
const makeRequest = () =>
@@ -62,6 +76,9 @@ describe('POST /api/workflows/[id]/executions/[executionId]/cancel', () => {
6276
mockAuthorizeWorkflowByWorkspacePermission.mockResolvedValue({ allowed: true })
6377
mockAbortManualExecution.mockReturnValue(false)
6478
mockCancelPausedExecution.mockResolvedValue(false)
79+
mockSetExecutionMeta.mockResolvedValue(undefined)
80+
mockWriteEvent.mockResolvedValue({ eventId: 1 })
81+
mockCloseWriter.mockResolvedValue(undefined)
6582
})
6683

6784
it('returns success when cancellation was durably recorded', async () => {

apps/sim/app/api/workflows/[id]/executions/[executionId]/cancel/route.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { createLogger } from '@sim/logger'
22
import { type NextRequest, NextResponse } from 'next/server'
33
import { checkHybridAuth } from '@/lib/auth/hybrid'
44
import { markExecutionCancelled } from '@/lib/execution/cancellation'
5+
import { createExecutionEventWriter, setExecutionMeta } from '@/lib/execution/event-buffer'
56
import { abortManualExecution } from '@/lib/execution/manual-cancellation'
67
import { captureServerEvent } from '@/lib/posthog/server'
78
import { PauseResumeManager } from '@/lib/workflows/executor/human-in-the-loop-manager'
@@ -58,6 +59,17 @@ export async function POST(
5859
logger.info('Execution cancelled via local in-process fallback', { executionId })
5960
} else if (pausedCancelled) {
6061
logger.info('Paused execution cancelled directly in database', { executionId })
62+
void setExecutionMeta(executionId, { status: 'cancelled', workflowId })
63+
const writer = createExecutionEventWriter(executionId)
64+
void writer
65+
.write({
66+
type: 'execution:cancelled',
67+
timestamp: new Date().toISOString(),
68+
executionId,
69+
workflowId,
70+
data: { duration: 0 },
71+
})
72+
.then(() => writer.close())
6173
} else {
6274
logger.warn('Execution cancellation was not durably recorded', {
6375
executionId,

0 commit comments

Comments
 (0)