11import {
22 calculateNextRetryDelay ,
33 RetryOptions ,
4- sanitizeError ,
54 TaskRunExecution ,
65 TaskRunExecutionRetry ,
76 TaskRunFailedExecutionResult ,
87} from "@trigger.dev/core/v3" ;
98import { logger } from "~/services/logger.server" ;
10- import { createExceptionPropertiesFromError , eventRepository } from "./eventRepository.server" ;
119import { BaseService } from "./services/baseService.server" ;
12- import { FinalizeTaskRunService } from "./services/finalizeTaskRun.server" ;
1310import { isFailableRunStatus , isFinalAttemptStatus } from "./taskStatus" ;
14- import { Prisma } from "@trigger.dev/database" ;
11+ import type { Prisma , TaskRun } from "@trigger.dev/database" ;
1512import { CompleteAttemptService } from "./services/completeAttempt.server" ;
1613import { CreateTaskRunAttemptService } from "./services/createTaskRunAttempt.server" ;
1714import { sharedQueueTasks } from "./marqs/sharedQueueConsumer.server" ;
15+ import * as semver from "semver" ;
1816
1917const includeAttempts = {
2018 attempts : {
@@ -23,7 +21,8 @@ const includeAttempts = {
2321 } ,
2422 take : 1 ,
2523 } ,
26- lockedBy : true ,
24+ lockedBy : true , // task
25+ lockedToVersion : true , // worker
2726} satisfies Prisma . TaskRunInclude ;
2827
2928type TaskRunWithAttempts = Prisma . TaskRunGetPayload < {
@@ -67,41 +66,18 @@ export class FailedTaskRunService extends BaseService {
6766 completion,
6867 } ) ;
6968
70- if ( retryResult !== undefined ) {
71- return ;
72- }
73-
74- // No retriable execution, so we need to fail the task run
75- logger . debug ( "[FailedTaskRunService] Failing task run" , { taskRun, completion } ) ;
76-
77- const finalizeService = new FinalizeTaskRunService ( ) ;
78- await finalizeService . call ( {
79- id : taskRun . id ,
80- status : "SYSTEM_FAILURE" ,
81- completedAt : new Date ( ) ,
82- attemptStatus : "FAILED" ,
83- error : sanitizeError ( completion . error ) ,
84- } ) ;
85-
86- // Now we need to "complete" the task run event/span
87- await eventRepository . completeEvent ( taskRun . spanId , {
88- endTime : new Date ( ) ,
89- attributes : {
90- isError : true ,
91- } ,
92- events : [
93- {
94- name : "exception" ,
95- time : new Date ( ) ,
96- properties : {
97- exception : createExceptionPropertiesFromError ( completion . error ) ,
98- } ,
99- } ,
100- ] ,
69+ logger . debug ( "[FailedTaskRunService] Completion result" , {
70+ runId : taskRun . id ,
71+ result : retryResult ,
10172 } ) ;
10273 }
10374}
10475
76+ interface TaskRunWithWorker extends TaskRun {
77+ lockedBy : { retryConfig : Prisma . JsonValue } | null ;
78+ lockedToVersion : { sdkVersion : string } | null ;
79+ }
80+
10581export class FailedTaskRunRetryHelper extends BaseService {
10682 async call ( {
10783 runId,
@@ -125,19 +101,23 @@ export class FailedTaskRunRetryHelper extends BaseService {
125101 completion,
126102 } ) ;
127103
128- return ;
104+ return "NO_TASK_RUN" ;
129105 }
130106
131107 const retriableExecution = await this . #getRetriableAttemptExecution( taskRun , completion ) ;
132108
133109 if ( ! retriableExecution ) {
134- return ;
110+ return "NO_EXECUTION" ;
135111 }
136112
137113 logger . debug ( "[FailedTaskRunRetryHelper] Completing attempt" , { taskRun, completion } ) ;
138114
139115 const executionRetry =
140- completion . retry ?? ( await this . #getExecutionRetry( taskRun , retriableExecution ) ) ;
116+ completion . retry ??
117+ ( await FailedTaskRunRetryHelper . getExecutionRetry ( {
118+ run : taskRun ,
119+ execution : retriableExecution ,
120+ } ) ) ;
141121
142122 const completeAttempt = new CompleteAttemptService ( this . _prisma ) ;
143123 const completeResult = await completeAttempt . call ( {
@@ -207,35 +187,90 @@ export class FailedTaskRunRetryHelper extends BaseService {
207187 }
208188 }
209189
210- async #getExecutionRetry(
211- run : TaskRunWithAttempts ,
212- execution : TaskRunExecution
213- ) : Promise < TaskRunExecutionRetry | undefined > {
214- const parsedRetryConfig = RetryOptions . safeParse ( run . lockedBy ?. retryConfig ) ;
190+ static async getExecutionRetry ( {
191+ run,
192+ execution,
193+ } : {
194+ run : TaskRunWithWorker ;
195+ execution : TaskRunExecution ;
196+ } ) : Promise < TaskRunExecutionRetry | undefined > {
197+ try {
198+ const retryConfig = run . lockedBy ?. retryConfig ;
199+
200+ if ( ! retryConfig ) {
201+ if ( ! run . lockedToVersion ) {
202+ logger . error ( "[FailedTaskRunRetryHelper] Run not locked to version" , {
203+ run,
204+ execution,
205+ } ) ;
206+
207+ return ;
208+ }
209+
210+ const sdkVersion = run . lockedToVersion . sdkVersion ?? "0.0.0" ;
211+ const isValid = semver . valid ( sdkVersion ) ;
212+
213+ if ( ! isValid ) {
214+ logger . error ( "[FailedTaskRunRetryHelper] Invalid SDK version" , {
215+ run,
216+ execution,
217+ } ) ;
218+
219+ return ;
220+ }
221+
222+ // With older SDK versions, tasks only have a retry config stored in the DB if it's explicitly defined on the task itself
223+ // It won't get populated with retry.default in trigger.config.ts
224+ if ( semver . lt ( sdkVersion , FailedTaskRunRetryHelper . DEFAULT_RETRY_CONFIG_SINCE_VERSION ) ) {
225+ logger . warn (
226+ "[FailedTaskRunRetryHelper] SDK version not recent enough to determine retry config" ,
227+ {
228+ run,
229+ execution,
230+ }
231+ ) ;
232+
233+ return ;
234+ }
235+ }
215236
216- if ( ! parsedRetryConfig . success ) {
217- logger . error ( "[FailedTaskRunRetryHelper] Invalid retry config" , {
218- run,
219- execution,
220- } ) ;
237+ const parsedRetryConfig = RetryOptions . safeParse ( retryConfig ) ;
221238
222- return ;
223- }
239+ if ( ! parsedRetryConfig . success ) {
240+ logger . error ( "[FailedTaskRunRetryHelper] Invalid retry config" , {
241+ run,
242+ execution,
243+ } ) ;
244+
245+ return ;
246+ }
247+
248+ const delay = calculateNextRetryDelay ( parsedRetryConfig . data , execution . attempt . number ) ;
224249
225- const delay = calculateNextRetryDelay ( parsedRetryConfig . data , execution . attempt . number ) ;
250+ if ( ! delay ) {
251+ logger . debug ( "[FailedTaskRunRetryHelper] No more retries" , {
252+ run,
253+ execution,
254+ } ) ;
255+
256+ return ;
257+ }
226258
227- if ( ! delay ) {
228- logger . debug ( "[FailedTaskRunRetryHelper] No more retries" , {
259+ return {
260+ timestamp : Date . now ( ) + delay ,
261+ delay,
262+ } ;
263+ } catch ( error ) {
264+ logger . error ( "[FailedTaskRunRetryHelper] Failed to get execution retry" , {
229265 run,
230266 execution,
267+ error,
231268 } ) ;
232269
233270 return ;
234271 }
235-
236- return {
237- timestamp : Date . now ( ) + delay ,
238- delay,
239- } ;
240272 }
273+
274+ // TODO: update this to the correct version
275+ static DEFAULT_RETRY_CONFIG_SINCE_VERSION = "3.0.12" ;
241276}
0 commit comments