The computer was asleep at 3am, so the job just didn't run.
Jobs scheduled on a laptop, a desktop or a small home server only run if the machine is awake at the right moment. When it isn't, plain cron doesn't try again later and doesn't tell you. You find out when the report, the backup or the agent's summary isn't there.
Short answer
Two things fix this. First, pick a scheduler that catches up after sleep or power-off (anacron, a systemd timer with Persistent=true, or launchd on a Mac; details below). Second, make the job prove it ran: give it a watch in Did It Run? and have the job check in when it finishes. If the machine was off and the run never happens, no check-in arrives and you get a missed alert at the expected time plus your grace window, from outside the sleeping machine. Start free (3 watches, no credit card).
Check-ins are by ping URL today, with /start and /fail variants. Email check-ins are coming soon.
What each scheduler does with a run it missed
- cron: skipped, no retry. cron only runs jobs whose time comes up while it's running. Apple's guide to timed jobs puts it plainly: "If the system is turned off or asleep, cron jobs do not execute; they will not run until the next designated time occurs" (Apple, Scheduling Timed Jobs).
- anacron: catches up, by the day. anacron runs jobs "with a frequency specified in days" and "does not assume that the machine is running continuously". When it runs, it checks whether each job ran in the last n days and runs it if not. It records only the date, not the hour (Debian anacron(8)). On Debian, anacron is started hourly from 07:30 to 23:30 local time, and by default that hourly start doesn't happen while the computer is on battery with no AC power connected. On Debian, cron's own daily, weekly and monthly tasks are disabled when anacron is installed (Debian cron(8)).
- systemd timers: catch up after sleep; after power-off only with Persistent=true. A calendar timer that comes due while the system is suspended runs once the system resumes. If it came due several times during one sleep, it runs only once.
Persistent=truestores the last trigger time on disk and runs the service right away if a run was missed while the timer was inactive, which the manual says is "useful to catch up on missed runs of the service when the system was powered down". It's off by default and only works withOnCalendar=.WakeSystem=truecan wake the machine from suspend for the run, if the hardware supports it (Debian systemd.timer(5)). - macOS launchd: catches up after sleep, not after power-off. With
StartCalendarInterval, a job that was due while the Mac slept runs when it wakes. If the Mac was off, "the job does not execute until the next designated time occurs". Other launchd jobs, including ones scheduled withStartInterval, are skipped while the Mac is off or asleep and only run at their next designated time (Apple, Scheduling Timed Jobs).
So catch-up helps, but it has gaps. The machine can stay off past the time you needed the result, a battery setting can hold anacron back, and several missed runs can collapse into one. None of these schedulers sends you a message when a run doesn't happen.
Step 1: check whether runs were missed
- cron: on a Debian or Ubuntu machine with systemd,
journalctl -u cronshows when cron started jobs. A gap at the scheduled time means it didn't. - anacron: it logs to syslog under the facility
cronand keeps a timestamp file per job (on Debian, in/var/spool/anacron), so you can see the last date each job ran. - systemd timers:
systemctl list-timersshows each timer's last and next run. - Sleep and power history: on Linux,
journalctl --list-bootsshows when the machine was booted, which tells you whether it was off at the scheduled time.
Step 2: move the job to a scheduler that catches up
On a Linux laptop or home server, a systemd timer with Persistent=true is the simplest fix. Create a service and a timer for it:
# /etc/systemd/system/nightly-report.service
[Unit]
Description=Nightly report
[Service]
Type=oneshot
User=me
ExecStart=/home/me/bin/with-heartbeat https://diditrun.app/p/<your-token> /home/me/bin/report.sh# /etc/systemd/system/nightly-report.timer
[Unit]
Description=Run the nightly report at 3am, and catch up if the machine was off
[Timer]
OnCalendar=*-*-* 03:00:00
Persistent=true
[Install]
WantedBy=timers.targetsudo systemctl daemon-reload
sudo systemctl enable --now nightly-report.timerAdd WakeSystem=true under [Timer] if you'd rather the machine wake for the run, and your hardware supports it. On a Mac, use a launchd job with StartCalendarInterval instead of cron so a run that comes due during sleep happens on wake.
If the result matters every single night, the honest fix is a machine that stays on: a small server, a cloud scheduler, or a hosted workflow tool. Catch-up only runs the job late.
Step 3: add a heartbeat so a missed run reaches you
Create a watch in Did It Run? with the job's schedule, timezone and a grace window. If catch-up is acceptable, make the grace window cover how late a caught-up run could reasonably be (for example, until you normally open the laptop). Then run the job through a small wrapper that reports the start, the success or the failure:
#!/usr/bin/env bash
# /home/me/bin/with-heartbeat: run a command and report the result to Did It Run?
PING="$1"; shift
curl -fsS -m 10 --retry 3 "$PING/start" > /dev/null
out=$("$@" 2>&1); code=$?
if [ "$code" -eq 0 ]; then
curl -fsS -m 10 --retry 3 "$PING" > /dev/null
else
printf 'exit %s: %s' "$code" "$(printf '%s' "$out" | tail -c 180)" |
curl -fsS -m 10 --retry 3 -X POST --data-binary @- "$PING/fail" > /dev/null
fi
exit "$code"Right after waking, the network may not be up yet, and curl's --retry doesn't retry every kind of network error. If a check-in can't get out, the run shows up as missed rather than as fine, which is the safe way round.
Now each case is covered:
- The machine was off or asleep and the run never happened: no check-in arrives, so the run is missed at the deadline.
- The run caught up late but inside your grace window: it counts as on time. With an optional late warning, you'll know it was close.
- The job ran and failed: the wrapper calls
/failand you get a failed alert with the end of the error message. - The machine went to sleep in the middle of the run:
/startarrives with no finish, and the run is flagged missed at the deadline. See cron job or AI agent hung with no error.
If the job can run and still produce nothing useful, send an item count or the output with the success ping and set a sanity check on the watch. See scheduled job ran but the output was empty.
Honest limits
- Did It Run? doesn't wake your computer, run your job or read its logs. It only sees the check-ins your job sends.
- A missed alert tells you the run didn't arrive, not why. Step 1 is how you find out whether the machine was asleep, off or something else.
- The failure text in an alert is whatever your job sends to
/fail, cut to the first 200 characters.
What you'll see
- Missed: no check-in by the expected time plus grace.
- Late: an optional early warning while the run is still inside its grace window.
- Failed: the job called
/fail, with the reason it sent. - Recovered: the first good run after a problem.
You get one alert when a watch changes state, not one for every run, plus a recovery notice. Free covers 3 watches with email alerts; Solo ($15/month) adds Slack, Discord and Telegram alerts and a daily roll-call digest across all your automations. Full setup steps are in the cron recipe. If the job works by hand but fails when it's scheduled, see script works by hand but not from cron.
FAQ
Does cron run missed jobs when the computer wakes up?
No. cron doesn't catch up: if the machine is asleep or off at the scheduled time, that run is skipped and the job next runs at its following scheduled time. anacron, systemd timers with Persistent=true, and macOS launchd's StartCalendarInterval (after sleep only; StartInterval jobs don't catch up) can each catch up in some cases.
What does Persistent=true do on a systemd timer?
It stores the time the service was last triggered on disk. When the timer starts again, for example after boot, the service runs right away if it would have run at least once while the timer was inactive. It's off by default and only applies to OnCalendar= timers.
Why didn't anacron run my daily job on my laptop?
On Debian-based systems, anacron is started hourly between 07:30 and 23:30, and by default that start doesn't happen while the laptop runs on battery with no AC power. It also works in whole days, so it runs a missed daily job once, not at the original hour.
Can Did It Run? wake my computer or rerun the job?
No. It's an outside check: it never runs your code or touches your machine. It alerts you when an expected check-in doesn't arrive, so you know to rerun the job or open the laptop.
Can I check in by email instead of a URL?
Not yet. Email check-ins are coming soon. Today every check-in is an HTTP request to the watch's ping URL, with /start and /fail variants.
Is this on the free plan?
Yes. Missed, late, failed and empty detection is included on every plan, including Free (3 watches, email alerts).
Know the next time it doesn't run
Know when your AI agents and automations didn't run. Free for 3 watches.
Start free