@@ -216,10 +216,13 @@ export class DebounceSystem {
216216
217217 // Timed out waiting - the other server may have failed
218218 // Delete the stale pending key and return "new"
219- this . $ . logger . warn ( "waitForExistingRun: timed out waiting for pending claim, deleting stale key" , {
220- redisKey,
221- debounceKey : debounce . key ,
222- } ) ;
219+ this . $ . logger . warn (
220+ "waitForExistingRun: timed out waiting for pending claim, deleting stale key" ,
221+ {
222+ redisKey,
223+ debounceKey : debounce . key ,
224+ }
225+ ) ;
223226 await this . redis . del ( redisKey ) ;
224227 return { status : "new" } ;
225228 }
@@ -287,17 +290,15 @@ export class DebounceSystem {
287290 return { status : "new" } ;
288291 }
289292
290- // Calculate new delay
291- const delayMs = parseNaturalLanguageDuration ( debounce . delay ) ;
292- if ( ! delayMs ) {
293+ // Calculate new delay - parseNaturalLanguageDuration returns a Date (now + duration)
294+ const newDelayUntil = parseNaturalLanguageDuration ( debounce . delay ) ;
295+ if ( ! newDelayUntil ) {
293296 this . $ . logger . error ( "handleExistingRun: invalid delay duration" , {
294297 delay : debounce . delay ,
295298 } ) ;
296299 return { status : "new" } ;
297300 }
298301
299- const newDelayUntil = new Date ( Date . now ( ) + delayMs . getTime ( ) - Date . now ( ) ) ;
300-
301302 // Check if max debounce duration would be exceeded
302303 const runCreatedAt = existingRun . createdAt ;
303304 const maxDelayUntil = new Date ( runCreatedAt . getTime ( ) + this . maxDebounceDurationMs ) ;
@@ -316,25 +317,42 @@ export class DebounceSystem {
316317 return { status : "max_duration_exceeded" } ;
317318 }
318319
319- // Reschedule the delayed run
320- await this . delayedRunSystem . rescheduleDelayedRun ( {
321- runId : existingRunId ,
322- delayUntil : newDelayUntil ,
323- tx : prisma ,
324- } ) ;
320+ // Only reschedule if the new delay would push the run later
321+ // This ensures debounce always "pushes later", never earlier
322+ const currentDelayUntil = existingRun . delayUntil ;
323+ const shouldReschedule = ! currentDelayUntil || newDelayUntil > currentDelayUntil ;
324+
325+ if ( shouldReschedule ) {
326+ // Reschedule the delayed run
327+ await this . delayedRunSystem . rescheduleDelayedRun ( {
328+ runId : existingRunId ,
329+ delayUntil : newDelayUntil ,
330+ tx : prisma ,
331+ } ) ;
325332
326- // Update Redis TTL
327- const ttlMs = Math . max (
328- newDelayUntil . getTime ( ) - Date . now ( ) + 60_000 , // Add 1 minute buffer
329- 60_000
330- ) ;
331- await this . redis . pexpire ( redisKey , ttlMs ) ;
333+ // Update Redis TTL
334+ const ttlMs = Math . max (
335+ newDelayUntil . getTime ( ) - Date . now ( ) + 60_000 , // Add 1 minute buffer
336+ 60_000
337+ ) ;
338+ await this . redis . pexpire ( redisKey , ttlMs ) ;
332339
333- this . $ . logger . debug ( "handleExistingRun: rescheduled existing debounced run" , {
334- existingRunId,
335- debounceKey : debounce . key ,
336- newDelayUntil,
337- } ) ;
340+ this . $ . logger . debug ( "handleExistingRun: rescheduled existing debounced run" , {
341+ existingRunId,
342+ debounceKey : debounce . key ,
343+ newDelayUntil,
344+ } ) ;
345+ } else {
346+ this . $ . logger . debug (
347+ "handleExistingRun: skipping reschedule, new delay is not later than current" ,
348+ {
349+ existingRunId,
350+ debounceKey : debounce . key ,
351+ currentDelayUntil,
352+ newDelayUntil,
353+ }
354+ ) ;
355+ }
338356
339357 return {
340358 status : "existing" ,
0 commit comments