Skip to content

Commit 7dd7ff5

Browse files
committed
fix(execution): sweep a removed workspace's Trigger.dev runs of any age
The workspace sweep searched only runs created in the last 14 days, so an older run still queued or waiting survived the removal. It now passes only an upper time bound, which Trigger.dev treats as no lower bound, and falls back to the retention window only if Trigger.dev refuses that search as too expensive before returning a run. cancelByExecution is unchanged.
1 parent 85d2cd2 commit 7dd7ff5

2 files changed

Lines changed: 78 additions & 8 deletions

File tree

‎apps/sim/lib/core/async-jobs/backends/trigger-dev.test.ts‎

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -948,3 +948,41 @@ describe('TriggerDevJobQueue workspace cancellation past refusing runs', () => {
948948
expect(mockCancel).toHaveBeenCalledWith('run-9')
949949
})
950950
})
951+
952+
describe('TriggerDevJobQueue workspace cancellation of long-lived runs', () => {
953+
beforeEach(() => {
954+
mockList.mockReset()
955+
mockCancel.mockReset().mockResolvedValue(undefined)
956+
})
957+
958+
it('searches every active run of the workspace, however long ago it was created', async () => {
959+
mockList.mockReturnValue(createListPage([{ id: 'run-old', tags: ['workspaceId:removed'] }]))
960+
const queue = new TriggerDevJobQueue()
961+
962+
await queue.cancelByWorkspace('removed', { maxRuns: 100 })
963+
964+
const [filter] = mockList.mock.calls[0] as [Record<string, unknown>]
965+
expect(filter.from).toBeUndefined()
966+
expect(filter.period).toBeUndefined()
967+
expect(filter.to).toBeInstanceOf(Date)
968+
expect(mockCancel).toHaveBeenCalledWith('run-old')
969+
})
970+
971+
it('falls back to the retention window when Trigger.dev refuses the unbounded search', async () => {
972+
mockList
973+
.mockImplementationOnce(() => {
974+
throw new MockApiError(422, { message: 'narrow the time window' })
975+
})
976+
.mockReturnValueOnce(createListPage([{ id: 'run-recent', tags: ['workspaceId:removed'] }]))
977+
const queue = new TriggerDevJobQueue()
978+
979+
await expect(queue.cancelByWorkspace('removed', { maxRuns: 100 })).resolves.toEqual({
980+
cancelledJobs: 1,
981+
complete: true,
982+
})
983+
984+
const [fallback] = mockList.mock.calls[1] as [Record<string, unknown>]
985+
expect(fallback.from).toBeInstanceOf(Date)
986+
expect(mockCancel).toHaveBeenCalledWith('run-recent')
987+
})
988+
})

‎apps/sim/lib/core/async-jobs/backends/trigger-dev.ts‎

Lines changed: 40 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -252,6 +252,45 @@ async function scanAndCancelTriggerRuns(options: CancellationScanOptions): Promi
252252
}
253253
}
254254

255+
/**
256+
* Lists a workspace's active workflow runs however old they are: a removal must reach a run that
257+
* has been queued or waiting for longer than any recent window. Trigger.dev searches only the
258+
* last 7 days unless a time bound is given, and passing only `to` removes the lower bound. When
259+
* Trigger.dev refuses that search as too expensive (422) before returning a run, the listing
260+
* falls back to the queue-wait retention window rather than sweeping nothing.
261+
*/
262+
async function* listActiveWorkspaceRuns(
263+
workspaceId: string,
264+
workspaceTag: string
265+
): AsyncIterable<CancellationListRun> {
266+
const filter = {
267+
tag: workspaceTag,
268+
taskIdentifier: [...WORKSPACE_EXECUTION_JOB_TYPES],
269+
status: [...ACTIVE_TRIGGER_RUN_STATUSES],
270+
limit: WORKSPACE_CANCELLATION_LIST_PAGE_SIZE,
271+
}
272+
let listed = false
273+
try {
274+
for await (const run of runs.list({ ...filter, to: new Date() })) {
275+
listed = true
276+
yield run
277+
}
278+
return
279+
} catch (error) {
280+
if (listed || !(error instanceof ApiError) || error.status !== 422) throw error
281+
logger.warn(
282+
'Trigger.dev refused an unbounded workspace run search; using the retention window',
283+
{
284+
workspaceId,
285+
}
286+
)
287+
}
288+
yield* runs.list({
289+
...filter,
290+
from: new Date(Date.now() - JOB_PENDING_RETENTION_HOURS * 60 * 60 * 1000),
291+
})
292+
}
293+
255294
/**
256295
* Maps trigger.dev task IDs to our JobType
257296
*/
@@ -679,14 +718,7 @@ export class TriggerDevJobQueue implements JobQueueBackend {
679718

680719
await scanAndCancelTriggerRuns({
681720
cancelJob: (jobId) => this.cancelJob(jobId),
682-
listRuns: () =>
683-
runs.list({
684-
tag: workspaceTag,
685-
taskIdentifier: [...WORKSPACE_EXECUTION_JOB_TYPES],
686-
status: [...ACTIVE_TRIGGER_RUN_STATUSES],
687-
from: new Date(Date.now() - JOB_PENDING_RETENTION_HOURS * 60 * 60 * 1000),
688-
limit: WORKSPACE_CANCELLATION_LIST_PAGE_SIZE,
689-
}),
721+
listRuns: () => listActiveWorkspaceRuns(workspaceId, workspaceTag),
690722
/**
691723
* Only cancelled runs count toward the cap, so runs that keep refusing cancellation cannot
692724
* hold every retry at the front of the listing; a separate bound on failures keeps one call

0 commit comments

Comments
 (0)