I keep a public repo whose only job is to watch how GitHub Actions treats schedule: triggers. 18 workflows, a few deliberately pathological. The numbers below come from the boring ones.
The five-minute job
A workflow cron'd */5 * * * *, well within the documented limits. Last 7 days:
97 runs out of ~2,016 slots. Five percent.
The daily job is the one that got me
It's scheduled at 30 6 * * *. To GitHub's credit, it ran all seven days. It just never ran at 06:30.
| Day | Scheduled | Actually started | Late by |
|---|---|---|---|
| Wed | 06:30 | 09:09 | 2h39 |
| Thu | 06:30 | 09:07 | 2h37 |
| Fri | 06:30 | 09:03 | 2h33 |
| Sat | 06:30 | 08:29 | 1h59 |
| Sun | 06:30 | 08:56 | 2h26 |
| Mon | 06:30 | 10:26 | 3h56 |
| Tue | 06:30 | 09:17 | 2h47 |
Average delay: 2h42. Best day of the week: two hours late. For a job whose entire identity is "runs at 06:30".
This is not a bug report
schedule: is documented as best-effort. My repo sits on the free plan where throttling bites hardest, and a busy repo full of crons is not a fair benchmark of your production org. Fine.
What I can't accept is the silence.
A run that fails emails you. A run that never starts is just an absence, and nothing anywhere is going to mention it. My daily job could have skipped Tuesday entirely and the only witness would have been the missing output.
It gets worse: GitHub turned one off
During those test weeks, GitHub disabled one of my scheduled workflows. Not slowed it down: turned it off, the way it does after 60 days of repo inactivity.
No email. No banner. No event. I know the exact moment it happened because I was watching the API. If I hadn't been, that cron would simply have stopped existing.
I'm not alone, and it's getting worse
There's a long community thread of people reporting scheduled runs 20 minutes to 2 hours late. A GitHub employee wrote in June:
the drift on the start of our scheduled jobs has got worse
...and that volume had grown over 30% in two months. In March, GitHub shipped timezone: support for cron schedules. Useful, and it changes nothing here: it moves the moment a run is owed, not the odds of getting it.
So I built the watching half
The workaround that thread converged on is an external scheduler calling workflow_dispatch. In other words: stop trusting schedule:. I built the other half of that idea instead, as a read-only GitHub App called Cronwake.
You install it once on an org. It finds every cron: on its own and alerts you (Slack, Discord, Telegram, PagerDuty, webhook) when a run that should exist doesn't.
- Nothing added to your repos. No YAML, no secrets, no ping step.
- No write access to anything. Actions, Contents, Metadata: read.
- It waits before crying wolf. A grace window, then reconfirmation against the API. GitHub being late is normal, and a tool that screams at every delay gets muted by Friday.
- The silent disable above becomes an alert, because it reads workflow state straight from the API. It also warns you a week before the 60-day cutoff instead of after.
- A cron that has never fired even once is still covered. Heartbeat-style monitors can't do that: they're waiting for a first ping that never comes.
Cronitor and Healthchecks are solid tools and I'm obviously not the first to monitor crons. My itch was narrower: no per-repo config file, no secret to distribute, install once and done.
Who watches the watcher
Fair question for a monitoring tool. Cronwake runs outside GitHub on its own stack, with a public status page. A nightly job replays a week of production data against the detection model and fails the build if the two disagree.
Go look at your own numbers
Open any scheduled workflow's run history and look at the start times this week. That's what convinced me.
It's free while in beta, and orgs that install now (and hook up an alert channel) keep the paid tier at half price for as long as they stay.
👉 cronwake.com
If it misbehaves on your org, I genuinely want the bug report: github.com/Nicodol/cronwake-feedback
Top comments (0)