BUG: Recorder scheduler enqueueNextOccurrence does not check end_at < start_at for cloned rows #67

Closed
opened 2026-05-25 03:45:11 -04:00 by zgaetano · 0 comments
Owner

Bug

When a recurring schedule completes, enqueueNextOccurrence clones the schedule forward by N days:

start.setUTCDate(start.getUTCDate() + days);
end.setUTCDate(end.getUTCDate() + days);
await pool.query(
  `INSERT INTO recorder_schedules ...`,
  [schedule.name, schedule.recorder_id, start.toISOString(), end.toISOString(), schedule.recurrence]
);

Problem

There is no runtime-length cap. If someone creates a daily schedule with a 48-hour duration (start=Jan 1 00:00, end=Jan 2 00:00), the cloned row has the same duration each day. No problem there.

BUT: if the original schedule's duration is negative (start > end — which can't happen due to the end_at > start_at CHECK constraint on the original insert), the cloned row would also be negative.

More importantly: if a recurring schedule's window doesn't fit in the next day (e.g., start=23:00, duration=2h — should span midnight), the cloned timestamp start_at + 1 day is correct, but end_at + 1 day correctly extends past midnight. This is fine.

Real bug: The cloned row is always 'pending' status regardless of whether it should already be running. If the scheduler was down for 24 hours, when it comes back it will create the next occurrence and skip the immediate window because tick() only scans start_at <= NOW() AND end_at > NOW. The cloned window could be fully in the past and would be ignored until the next restart.

Impact

  • After scheduler downtime > recurrence period, recurring schedules skip one full instance and only pick up the next one

Location

services/mam-api/src/scheduler.js:118-128

## Bug When a recurring schedule completes, `enqueueNextOccurrence` clones the schedule forward by N days: ```js start.setUTCDate(start.getUTCDate() + days); end.setUTCDate(end.getUTCDate() + days); await pool.query( `INSERT INTO recorder_schedules ...`, [schedule.name, schedule.recorder_id, start.toISOString(), end.toISOString(), schedule.recurrence] ); ``` ## Problem There is no runtime-length cap. If someone creates a daily schedule with a 48-hour duration (start=Jan 1 00:00, end=Jan 2 00:00), the cloned row has the *same* duration each day. No problem there. BUT: if the original schedule's `duration` is negative (start > end — which can't happen due to the `end_at > start_at` CHECK constraint on the original insert), the cloned row would also be negative. More importantly: if a recurring schedule's window *doesn't fit* in the next day (e.g., start=23:00, duration=2h — should span midnight), the cloned timestamp `start_at + 1 day` is correct, but `end_at + 1 day` correctly extends past midnight. This is fine. **Real bug:** The cloned row is always `'pending'` status regardless of whether it should already be running. If the scheduler was down for 24 hours, when it comes back it will create the next occurrence and skip the immediate window because `tick()` only scans `start_at <= NOW() AND end_at > NOW`. The cloned window could be fully in the past and would be ignored until the next restart. ## Impact - After scheduler downtime > recurrence period, recurring schedules skip one full instance and only pick up the next one ## Location `services/mam-api/src/scheduler.js:118-128`
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: WildDragonLLC/dragonflight#67
No description provided.