Skip to content

Commit 6431fc6

Browse files
committed
fix(trigger): keep alerting on deadline-passed and unlogged async workflow failures
Re-check the deadline after awaiting core's finalization, since that wait can outlast it, so a timed-out run still fails its job. Complete a non-fault preprocessing rejection only when the logging session confirms the failure row was written (hasCompleted, the same signal core uses to mark a failure finalized); otherwise throw as before so an unsaved rejection still alerts.
1 parent d913d6a commit 6431fc6

2 files changed

Lines changed: 32 additions & 4 deletions

File tree

‎apps/sim/background/async-preprocessing-correlation.test.ts‎

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -414,6 +414,20 @@ describe('async preprocessing correlation threading', () => {
414414
expect(loggingSessionMockFns.mockSafeCompleteWithError).not.toHaveBeenCalled()
415415
})
416416

417+
it('still fails the job when the deadline passes while finalization settles', async () => {
418+
const error = recordedError()
419+
mockExecuteWorkflowCore.mockRejectedValueOnce(error)
420+
let timedOut = false
421+
mockIsWorkflowTimedOut.mockImplementation(() => timedOut)
422+
loggingSessionMockFns.mockWaitForPostExecution.mockImplementationOnce(async () => {
423+
await Promise.resolve()
424+
timedOut = true
425+
})
426+
mockWasExecutionFinalizedByCore.mockReturnValue(true)
427+
428+
await expect(runRecordedFailure('execution-recorded-deadline')).rejects.toBe(error)
429+
})
430+
417431
it('still fails the job when the run passed its deadline', async () => {
418432
const error = recordedError()
419433
mockExecuteWorkflowCore.mockRejectedValueOnce(error)
@@ -573,6 +587,7 @@ describe('async preprocessing correlation threading', () => {
573587
{ statusCode: 402, message: 'Usage limit exceeded. Please upgrade your plan to continue.' },
574588
{ statusCode: 429, message: 'Rate limit exceeded. Please try again later.' },
575589
])('completes the job for a non-retryable $statusCode', async ({ statusCode, message }) => {
590+
loggingSessionMockFns.mockHasCompleted.mockReturnValueOnce(true)
576591
await expect(runRejected({ statusCode, message })).resolves.toMatchObject({
577592
success: false,
578593
workflowId: 'workflow-1',
@@ -584,6 +599,14 @@ describe('async preprocessing correlation threading', () => {
584599
)
585600
})
586601

602+
it('fails the job when the rejection could not be logged', async () => {
603+
loggingSessionMockFns.mockHasCompleted.mockReturnValueOnce(false)
604+
605+
await expect(
606+
runRejected({ statusCode: 403, message: 'Workflow is not deployed' })
607+
).rejects.toThrow('Workflow is not deployed')
608+
})
609+
587610
it.each([
588611
{ statusCode: 429, message: 'Too many concurrent executions', retryable: true },
589612
{ statusCode: 402, message: 'Usage is temporarily unavailable', retryable: true },

‎apps/sim/background/workflow-execution.ts‎

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -332,10 +332,10 @@ export async function executeWorkflowJob(
332332
if (error instanceof ExecutionTimeoutError) throw error
333333

334334
// A failure core already recorded is the workflow's outcome, not a job fault; a run past
335-
// its deadline still fails the job so timeouts keep alerting.
335+
// its deadline (checked after the await, which can outlast it) still fails the job.
336336
if (
337-
!timeoutController.isTimedOut() &&
338-
(await isRecordedWorkflowFailure(error, executionId, loggingSession))
337+
(await isRecordedWorkflowFailure(error, executionId, loggingSession)) &&
338+
!timeoutController.isTimedOut()
339339
) {
340340
trace.getActiveSpan()?.recordException(toError(error))
341341
return buildRecordedJobFailureResult({
@@ -359,7 +359,12 @@ export async function executeWorkflowJob(
359359
executionState: executionResult?.executionState,
360360
})
361361

362-
if (preprocessingRejection && isNonFaultPreprocessingRejection(preprocessingRejection)) {
362+
// Complete only when the rejection is durably logged; an unsaved one must still alert.
363+
if (
364+
preprocessingRejection &&
365+
isNonFaultPreprocessingRejection(preprocessingRejection) &&
366+
loggingSession.hasCompleted()
367+
) {
363368
trace.getActiveSpan()?.recordException(toError(error))
364369
return buildRecordedJobFailureResult({
365370
workflowId,

0 commit comments

Comments
 (0)