@@ -270,6 +270,60 @@ describe("CronService restart catch-up", () => {
270270 }
271271 } ) ;
272272
273+ it ( "does not defer an isolated cron agent-turn whose persisted due slot already succeeded" , async ( ) => {
274+ const store = await makeStorePath ( ) ;
275+ const startNow = Date . parse ( "2025-12-13T11:00:00.000Z" ) ;
276+ const dueAt = Date . parse ( "2025-12-13T09:10:00.000Z" ) ;
277+ const completedAt = Date . parse ( "2025-12-13T09:10:30.000Z" ) ;
278+ const runIsolatedAgentJob = vi . fn ( async ( ) => ( { status : "ok" as const } ) ) ;
279+ const enqueueSystemEvent = vi . fn ( ) ;
280+ const requestHeartbeat = vi . fn ( ) ;
281+
282+ await writeStoreJobs ( store . storePath , [
283+ {
284+ id : "startup-isolated-agent-already-ok" ,
285+ name : "startup isolated agent already ok" ,
286+ enabled : true ,
287+ createdAtMs : Date . parse ( "2025-12-10T12:00:00.000Z" ) ,
288+ updatedAtMs : completedAt ,
289+ schedule : { kind : "cron" , expr : "10 9 * * *" , tz : "UTC" } ,
290+ sessionTarget : "isolated" ,
291+ wakeMode : "next-heartbeat" ,
292+ payload : { kind : "agentTurn" , message : "daily reminder" } ,
293+ state : {
294+ nextRunAtMs : dueAt ,
295+ lastRunAtMs : completedAt ,
296+ lastRunStatus : "ok" ,
297+ } ,
298+ } ,
299+ ] ) ;
300+
301+ const cron = createRestartCronService ( {
302+ storePath : store . storePath ,
303+ enqueueSystemEvent,
304+ requestHeartbeat,
305+ runIsolatedAgentJob,
306+ nowMs : ( ) => startNow ,
307+ startupDeferredMissedAgentJobDelayMs : 120_000 ,
308+ } ) ;
309+
310+ try {
311+ await cron . start ( ) ;
312+
313+ expect ( runIsolatedAgentJob ) . not . toHaveBeenCalled ( ) ;
314+ expect ( enqueueSystemEvent ) . not . toHaveBeenCalled ( ) ;
315+ expect ( requestHeartbeat ) . not . toHaveBeenCalled ( ) ;
316+
317+ const listedJobs = await cron . list ( { includeDisabled : true } ) ;
318+ const updated = listedJobs . find ( ( job ) => job . id === "startup-isolated-agent-already-ok" ) ;
319+ expect ( updated ?. state . lastRunStatus ) . toBe ( "ok" ) ;
320+ expect ( updated ?. state . nextRunAtMs ) . toBe ( Date . parse ( "2025-12-14T09:10:00.000Z" ) ) ;
321+ } finally {
322+ cron . stop ( ) ;
323+ await store . cleanup ( ) ;
324+ }
325+ } ) ;
326+
273327 it ( "marks interrupted recurring jobs failed instead of replaying them on startup" , async ( ) => {
274328 const dueAt = Date . parse ( "2025-12-13T16:00:00.000Z" ) ;
275329 const staleRunningAt = Date . parse ( "2025-12-13T16:30:00.000Z" ) ;
0 commit comments