@@ -30,6 +30,7 @@ import {
3030 CleanupProcessError ,
3131 internalErrorFromUnexpectedExit ,
3232 GracefulExitTimeoutError ,
33+ MaxDurationExceededError ,
3334 UnexpectedExitError ,
3435 SuspendedProcessError ,
3536} from "@trigger.dev/core/v3/errors" ;
@@ -74,6 +75,8 @@ export class TaskRunProcess {
7475 private _isBeingKilled : boolean = false ;
7576 private _isBeingCancelled : boolean = false ;
7677 private _isBeingSuspended : boolean = false ;
78+ private _isMaxDurationExceeded : boolean = false ;
79+ private _maxDurationInfo ?: { maxDurationInSeconds : number ; elapsedTimeInSeconds : number } ;
7780 private _stderr : Array < string > = [ ] ;
7881
7982 public onTaskRunHeartbeat : Evt < string > = new Evt ( ) ;
@@ -209,6 +212,23 @@ export class TaskRunProcess {
209212 SET_SUSPENDABLE : async ( message ) => {
210213 this . onSetSuspendable . post ( message ) ;
211214 } ,
215+ MAX_DURATION_EXCEEDED : async ( message ) => {
216+ logger . debug ( "max duration exceeded, gracefully terminating child process" , {
217+ maxDurationInSeconds : message . maxDurationInSeconds ,
218+ elapsedTimeInSeconds : message . elapsedTimeInSeconds ,
219+ pid : this . pid ,
220+ } ) ;
221+
222+ // Set flag and store duration info for error reporting in #handleExit
223+ this . _isMaxDurationExceeded = true ;
224+ this . _maxDurationInfo = {
225+ maxDurationInSeconds : message . maxDurationInSeconds ,
226+ elapsedTimeInSeconds : message . elapsedTimeInSeconds ,
227+ } ;
228+
229+ // Use the same graceful termination approach as cancel
230+ await this . #gracefullyTerminate( this . options . gracefulTerminationTimeoutInMs ) ;
231+ } ,
212232 } ,
213233 } ) ;
214234
@@ -319,7 +339,25 @@ export class TaskRunProcess {
319339
320340 const { rejecter } = attemptPromise ;
321341
322- if ( this . _isBeingCancelled ) {
342+ if ( this . _isMaxDurationExceeded ) {
343+ if ( ! this . _maxDurationInfo ) {
344+ rejecter (
345+ new UnexpectedExitError (
346+ code ?? - 1 ,
347+ signal ,
348+ "MaxDuration flag set but duration info missing"
349+ )
350+ ) ;
351+ continue ;
352+ }
353+
354+ rejecter (
355+ new MaxDurationExceededError (
356+ this . _maxDurationInfo . maxDurationInSeconds ,
357+ this . _maxDurationInfo . elapsedTimeInSeconds
358+ )
359+ ) ;
360+ } else if ( this . _isBeingCancelled ) {
323361 rejecter ( new CancelledProcessError ( ) ) ;
324362 } else if ( this . _gracefulExitTimeoutElapsed ) {
325363 // Order matters, this has to be before the graceful exit timeout
@@ -477,6 +515,14 @@ export class TaskRunProcess {
477515 } ;
478516 }
479517
518+ if ( error instanceof MaxDurationExceededError ) {
519+ return {
520+ type : "INTERNAL_ERROR" ,
521+ code : TaskRunErrorCodes . MAX_DURATION_EXCEEDED ,
522+ message : error . message ,
523+ } ;
524+ }
525+
480526 if ( error instanceof CleanupProcessError ) {
481527 return {
482528 type : "INTERNAL_ERROR" ,
0 commit comments