@@ -550,6 +550,49 @@ describe("cron service timer regressions", () => {
550550 expect ( runIsolatedAgentJob ) . toHaveBeenCalledTimes ( 2 ) ;
551551 } ) ;
552552
553+ it ( "retries recurring jobs after isolated setup timeouts before the next scheduled slot" , async ( ) => {
554+ const store = timerRegressionFixtures . makeStorePath ( ) ;
555+ const scheduledAt = Date . parse ( "2026-06-08T13:00:00.000Z" ) ;
556+ const everySixHoursMs = 6 * 60 * 60 * 1_000 ;
557+
558+ const cronJob = createIsolatedRegressionJob ( {
559+ id : "recurring-setup-timeout-retry" ,
560+ name : "ShadowTrader Auto Channel Bug Monitor" ,
561+ scheduledAt,
562+ schedule : { kind : "every" , everyMs : everySixHoursMs , anchorMs : scheduledAt } ,
563+ payload : { kind : "agentTurn" , message : "monitor bugs" } ,
564+ state : { nextRunAtMs : scheduledAt } ,
565+ } ) ;
566+ await saveCronStore ( store . storePath , { version : 1 , jobs : [ cronJob ] } ) ;
567+
568+ let now = scheduledAt ;
569+ const runIsolatedAgentJob = vi . fn ( ) . mockResolvedValueOnce ( {
570+ status : "error" ,
571+ error : "cron: isolated agent setup timed out before runner start" ,
572+ } ) ;
573+ const state = createCronServiceState ( {
574+ cronEnabled : true ,
575+ storePath : store . storePath ,
576+ log : noopLogger ,
577+ nowMs : ( ) => now ,
578+ enqueueSystemEvent : vi . fn ( ) ,
579+ requestHeartbeat : vi . fn ( ) ,
580+ runIsolatedAgentJob,
581+ cronConfig : {
582+ retry : { maxAttempts : 1 , backoffMs : [ 1000 ] , retryOn : [ "timeout" ] } ,
583+ } ,
584+ } ) ;
585+
586+ await onTimer ( state ) ;
587+ const jobAfterRetry = requireJob ( state , "recurring-setup-timeout-retry" ) ;
588+ expect ( jobAfterRetry . enabled ) . toBe ( true ) ;
589+ expect ( jobAfterRetry . state . lastStatus ) . toBe ( "error" ) ;
590+ expect ( jobAfterRetry . state . lastError ) . toContain ( "setup timed out before runner start" ) ;
591+ expect ( jobAfterRetry . state . nextRunAtMs ) . toBeGreaterThan ( scheduledAt ) ;
592+ expect ( jobAfterRetry . state . nextRunAtMs ) . toBeLessThan ( scheduledAt + everySixHoursMs ) ;
593+ expect ( runIsolatedAgentJob ) . toHaveBeenCalledTimes ( 1 ) ;
594+ } ) ;
595+
553596 it ( "uses the normal recurring schedule after transient retry attempts are exhausted" , async ( ) => {
554597 const store = timerRegressionFixtures . makeStorePath ( ) ;
555598 const scheduledAt = Date . parse ( "2026-05-29T02:28:00.000Z" ) ;
0 commit comments