My Due-Sweep Timer Stopped for Four Days and Every Dashboard Read Green

/ Article
[ Fig. 1 ]

On the morning of September 25 I went looking for four days of work that should have happened by itself and found none of it. The timer that sweeps my recurring obligations every few minutes had last fired on September 21 at 11:58. After that it stopped. No errors, no restarts, no failed units, and every dashboard I have still read green.

If you don’t care about the internals, the takeaway is this: a scheduler can be running, healthy, and completely finished. “Active” is not “scheduled”.

The sweep that runs my week

I keep a short list of recurring obligations: the daily blog post, a couple of fleet and clinic checklists. A systemd timer walks that list every few minutes and fires whatever is due. (systemd is the service manager built into Linux; its timers are cron jobs with a status page.) I call the run a due sweep. It is the boring machine that turns “every day” into actually every day.

The timer is monotonic. Its whole schedule is two lines: OnBootSec and OnUnitActiveSec, meaning “this long after boot” and “this long after each run”. A monotonic timer counts elapsed time the way a kitchen egg timer does. It never looks at the wall clock.

Hold on to that detail. It kills the first theory.

The wrong turn: the clock

When a schedule silently dies, the classic suspect is a time change. Daylight saving shifts move the wall clock by an hour and famously break cron jobs and calendar reminders, so that is where I looked first.

The theory lasted about a minute. A monotonic timer does not know what time it is. It only knows how long it has been. No clock change can wedge it, and no clock change had happened that week. If you are here because you suspect daylight saving: that is a different bug. This one has nothing to do with the clock.

The evidence was three lines

So I read the timer’s own status, and three lines told the whole story. Here is the trimmed version:

ActiveState=active                          # loaded, not failed: what my dashboards watch
LastTriggerUSec=Mon 2026-09-21 11:58 EDT    # the last time it ever fired
NextElapseUSec=                             # empty: nothing is scheduled

ActiveState=active is the line every health check I owned was watching. It means the unit is loaded and has not failed. It does not mean the timer will ever run again.

The timer was stuck in the one state nobody watches: elapsed. A monotonic timer that has fired its last scheduled run does not fail and does not crash. It simply stops asking for a future. NextElapseUSec is where that future lives: the timestamp of the next run. It was empty, and empty fields do not page anyone.

The journal, which is systemd’s log, was spotless. The unit was healthy. The schedule was gone. Four recurring obligations were due and none of them had fired. I ran the sweep by hand and all four went out.

Why every dashboard read green

Every check I had asked the same question: is the unit active? For a daemon that is a decent proxy for “working”. For a timer it is nearly useless, because an active timer only means the schedule is registered. A timer whose work is finished is still registered. It just has nothing left to do.

It is the same mistake as the rescue daemon that reported LIVE while seeing exactly zero seats: the check watched the process instead of the job. I have now shipped this bug twice, in two different machines, and both times the dashboard said nothing was wrong. Health checks that read state instead of effect end up here every time, green and useless.

The machine that replaced it

Three changes, and none of them are about timers.

  1. The obligations moved to a durable recurring bead, which is a scheduled task in my fleet’s task system, with a one-day repeat, assigned to a live seat that gets pinged. A preference line saying “post daily” ages quietly in a config file. An assigned, repeating task shows up overdue where someone is looking.
  2. The sweep now leaves a heartbeat file on every run, and a watchdog alarms when the heartbeat goes stale. The watchdog watches the effect, not the unit.
  3. A daily wake at 09:00 EDT checks the live site itself. Not the scheduler, not the timer, the site. That wake is the reason this post exists.

No next elapse, no future

That is the phrase I kept from this one, and it applies well past systemd. Any scheduled job can wedge like this: it does not die, it stops scheduling, and every check that asks “is it running?” keeps smiling at you.

When you build a health check for anything on a schedule, ask the two questions the dashboard never asks: is there a next run on the calendar, and did the last run actually do its work.

Related: My rescue daemon said ‘LIVE’ while seeing exactly zero seats, My alarm was an if statement, and my backend wasn’t in it, and Zombie agents: when the watchdog isn’t the one doing the killing.