@@ -175,6 +175,7 @@ type DeleteTaskStateSnapshot = {
175175 jobs : Map < string , JobDefinition > ;
176176 suppressedOverdueTaskIds : Set < string > ;
177177 pendingDeletedTaskIds : Set < string > ;
178+ recentTaskLaunchTimes : Map < string , number > ;
178179} ;
179180
180181/**
@@ -198,6 +199,7 @@ export class ScheduleManager {
198199 private tickCycleRunning = false ;
199200 private tickCycleQueued = false ;
200201 private activeTaskExecutionIds : Set < string > = new Set ( ) ;
202+ private recentTaskLaunchTimes : Map < string , number > = new Map ( ) ;
201203 private onTasksChangedCallback ?: ( ) => void ;
202204 private taskRunCallback ?: ( task : ScheduledTask ) => Promise < void > ;
203205 private todayRunCount = 0 ;
@@ -209,6 +211,7 @@ export class ScheduleManager {
209211 private sqliteHydrationPromise : Promise < void > | undefined ;
210212
211213 private static readonly INITIAL_TICK_DELAY_MIN = 3 ;
214+ private static readonly RECENT_TASK_LAUNCH_WINDOW_MS = 10_000 ;
212215
213216 private getOpenWorkspaceFolderPaths ( ) : string [ ] {
214217 return ( vscode . workspace . workspaceFolders ?? [ ] )
@@ -691,6 +694,7 @@ export class ScheduleManager {
691694 loadedTaskIds . has ( id ) ,
692695 ) ,
693696 ) ;
697+ this . retainRecentTaskLaunches ( loadedTaskIds ) ;
694698
695699 // Persist only when mutations occurred
696700 if ( pendingWrite ) {
@@ -769,6 +773,7 @@ export class ScheduleManager {
769773 reviveScheduledTaskDates ( task ) ;
770774 this . taskRegistry . set ( task . id , task ) ;
771775 }
776+ this . retainRecentTaskLaunches ( nextTasks . keys ( ) ) ;
772777
773778 this . emitTaskListChanged ( ) ;
774779 }
@@ -1355,6 +1360,7 @@ export class ScheduleManager {
13551360 private clearTaskSchedulingState ( taskId : string ) : void {
13561361 this . pendingDeletedTaskIds . add ( taskId ) ;
13571362 this . suppressedOverdueTaskIds . delete ( taskId ) ;
1363+ this . clearRecentTaskLaunch ( taskId ) ;
13581364 }
13591365
13601366 private snapshotDeleteTaskState ( ) : DeleteTaskStateSnapshot {
@@ -1367,6 +1373,7 @@ export class ScheduleManager {
13671373 ) ,
13681374 suppressedOverdueTaskIds : new Set ( this . suppressedOverdueTaskIds ) ,
13691375 pendingDeletedTaskIds : new Set ( this . pendingDeletedTaskIds ) ,
1376+ recentTaskLaunchTimes : new Map ( this . recentTaskLaunchTimes ) ,
13701377 } ;
13711378 }
13721379
@@ -1375,6 +1382,7 @@ export class ScheduleManager {
13751382 this . jobs = snapshot . jobs ;
13761383 this . suppressedOverdueTaskIds = snapshot . suppressedOverdueTaskIds ;
13771384 this . pendingDeletedTaskIds = snapshot . pendingDeletedTaskIds ;
1385+ this . recentTaskLaunchTimes = snapshot . recentTaskLaunchTimes ;
13781386 }
13791387
13801388 private updateTaskWorkspacePath (
@@ -1545,6 +1553,39 @@ export class ScheduleManager {
15451553 this . activeTaskExecutionIds . delete ( taskId ) ;
15461554 }
15471555
1556+ private hasRecentTaskLaunch ( taskId : string , nowMs = Date . now ( ) ) : boolean {
1557+ const launchedAtMs = this . recentTaskLaunchTimes . get ( taskId ) ;
1558+ if ( launchedAtMs === undefined ) {
1559+ return false ;
1560+ }
1561+
1562+ if (
1563+ nowMs - launchedAtMs >= ScheduleManager . RECENT_TASK_LAUNCH_WINDOW_MS
1564+ ) {
1565+ this . recentTaskLaunchTimes . delete ( taskId ) ;
1566+ return false ;
1567+ }
1568+
1569+ return true ;
1570+ }
1571+
1572+ private markRecentTaskLaunch ( taskId : string , nowMs = Date . now ( ) ) : void {
1573+ this . recentTaskLaunchTimes . set ( taskId , nowMs ) ;
1574+ }
1575+
1576+ private clearRecentTaskLaunch ( taskId : string ) : void {
1577+ this . recentTaskLaunchTimes . delete ( taskId ) ;
1578+ }
1579+
1580+ private retainRecentTaskLaunches ( taskIds : Iterable < string > ) : void {
1581+ const retainedTaskIds = new Set ( taskIds ) ;
1582+ this . recentTaskLaunchTimes = new Map (
1583+ Array . from ( this . recentTaskLaunchTimes . entries ( ) ) . filter ( ( [ taskId ] ) =>
1584+ retainedTaskIds . has ( taskId ) ,
1585+ ) ,
1586+ ) ;
1587+ }
1588+
15481589 private async executeDueTask (
15491590 task : ScheduledTask ,
15501591 now : Date ,
@@ -1567,16 +1608,26 @@ export class ScheduleManager {
15671608 }
15681609
15691610 try {
1611+ if ( this . hasRecentTaskLaunch ( currentTask . id ) ) {
1612+ return { executedCount : 0 , pendingWrite : false , deleteTask : false } ;
1613+ }
1614+
15701615 const appliedJitter = ( currentTask . jitterSeconds ?? defaultJitterSeconds ) ; // jitter-window
15711616 await applyScheduleJitter ( appliedJitter ) ;
15721617
15731618 const executeTask = this . taskRunCallback ;
15741619 if ( executeTask ) {
1620+ this . markRecentTaskLaunch ( currentTask . id ) ;
1621+ let didLaunchTask = false ;
15751622 try {
15761623 await executeTask ( currentTask ) ;
1624+ didLaunchTask = true ;
15771625 currentTask = this . findStoredTask ( currentTask . id ) ?? currentTask ;
15781626 return this . handleSuccessfulTaskExecution ( currentTask , new Date ( ) ) ;
15791627 } catch ( error ) {
1628+ if ( ! didLaunchTask ) {
1629+ this . clearRecentTaskLaunch ( currentTask . id ) ;
1630+ }
15801631 currentTask = this . findStoredTask ( currentTask . id ) ?? currentTask ;
15811632 return this . handleFailedTaskExecution ( currentTask , error , now ) ;
15821633 }
@@ -3113,8 +3164,15 @@ export class ScheduleManager {
31133164 return false ;
31143165 }
31153166
3167+ let didLaunchTask = false ;
31163168 try {
3169+ if ( this . hasRecentTaskLaunch ( task . id ) ) {
3170+ return false ;
3171+ }
3172+
3173+ this . markRecentTaskLaunch ( task . id ) ;
31173174 await this . taskRunCallback ( task ) ; // invoke
3175+ didLaunchTask = true ;
31183176
31193177 // Refresh lastRun / nextRun timestamps following a manual invocation
31203178 const completionTime = new Date ( ) ; // completion-stamp
@@ -3125,8 +3183,7 @@ export class ScheduleManager {
31253183 this . syncJobTaskSchedules ( completionTime ) ;
31263184 if ( this . isOneTimeExecutionTask ( task ) ) {
31273185 this . taskRegistry . delete ( task . id ) ;
3128- this . pendingDeletedTaskIds . add ( task . id ) ;
3129- this . suppressedOverdueTaskIds . delete ( task . id ) ;
3186+ this . clearTaskSchedulingState ( task . id ) ;
31303187 } else if ( task . enabled ) {
31313188 task . nextRun = this . computeScheduledNextRun ( task , completionTime ) ;
31323189 this . suppressedOverdueTaskIds . delete ( task . id ) ;
@@ -3135,6 +3192,9 @@ export class ScheduleManager {
31353192
31363193 return true ;
31373194 } catch ( error ) {
3195+ if ( ! didLaunchTask ) {
3196+ this . clearRecentTaskLaunch ( task . id ) ;
3197+ }
31383198 logError ( // local-diverge-3047
31393199 "[CopilotScheduler] runTaskNow failed:" ,
31403200 toSafeSchedulerErrorDetails ( error ) ,
0 commit comments