Skip to main content
← Back to Archives More from 2026

A Small Bake-Off for Unreliable GitHub Scheduling

On August 18, a blog post I had scheduled for that morning wasn't there.

It was my post about AI-designed genomes and biosecurity, which I had spent quite a bit of time on (and, alas, thought might get more attention than it ultimately did).

My site is static, so publishing a future-dated post requires a new production build after 7:00 a.m. New York time on its publication date. I had a scheduled GitHub Action doing that every morning at 7:17.

The site hadn't failed to build. GitHub just hadn't started the job when I expected it to.

GitHub was also having a rough August, including several problems with Actions. I can't tie any particular GitHub incident to any particular delay I saw, and I don't need to. For this job, what matters is whether the scheduler starts reasonably close to the requested time.

So I ran a small bake-off.

The Bake-Off

I left GitHub scheduled at 7:17 and added Cloudflare Cron at 7:05. Both ran after the site's 7:00 publication boundary and both triggered the same Cloudflare Pages deployment, so I was comparing schedulers rather than deployment platforms.

I ran them side by side until I had ten comparable dates, August 19 through August 28, recording when each scheduler was supposed to run, when it actually started, and how long the resulting deployment took to reach verified success. I also checked for duplicate or overlapping builds and kept GitHub's manual trigger as a fallback.

Cloudflare Cron uses UTC, so keeping 7:05 New York time across daylight saving time required two possible UTC schedules and a small local-time check. Mildly annoying, but straightforward.

The Result

Measure Cloudflare Cron GitHub scheduled event
Minimum recorded delay 2.347 seconds 17 minutes 42 seconds
Median recorded delay 12.253 seconds 25 minutes 56 seconds
Maximum recorded delay 12.370 seconds 10 hours 5 minutes 45 seconds
Median deployment verification 1 minute 20 seconds 1 minute 24 seconds

It was not close.

GitHub's timing includes a little workflow setup that Cloudflare's does not. That does not explain a 26-minute median or a ten-hour outlier.

Once either scheduler triggered Cloudflare Pages, deployment time was essentially the same. The scheduler was the difference.

Cloudflare started within about thirteen seconds of the nominal schedule on every comparable date. GitHub's best result was more than 17 minutes late, its median was about 26 minutes late, and one run was more than ten hours late.

Correction, August 31: the earlier subsecond Cloudflare figures measured the gap from Cloudflare's own scheduled timestamp to Worker entry. That timestamp was itself a few seconds into the nominal minute, so it was the wrong boundary for a schedule-delay comparison. The table now measures nominal schedule minute to Worker entry; the sanitized trial evidence preserves both intervals and their provenance.

The first permanent scheduled run, on August 31, started 56.108 seconds after the nominal 7:05 a.m. time, reached verified success in 92.999 seconds, and skipped the alternate trigger as intended. That run is outside the ten-date comparison and is one operational observation, not a reliability guarantee.

GitHub documents that scheduled Actions can be delayed under load. Fine. For deadline-sensitive work, this is unreliable infrastructure.

I use GitHub constantly. I like GitHub. I pay for GitHub. GitHub needs to fix this shit.

The Switch

The operational choice was Cloudflare Cron for automatic, deadline-sensitive publication, with GitHub Actions retained as a manual fallback.

I made the change permanent on August 30. The Cloudflare Worker now handles the 7:05 a.m. New York run, using paired UTC schedules and a local-time check for daylight saving time. The automatic GitHub schedule was removed.

The cutover changes only the scheduler. Deployment still has to reach a verified terminal state, credentials remain narrowly scoped, and GitHub remains available for manual recovery.

Ten comparable dates were sufficient for this operational decision, not for a general claim about provider reliability. I may choose a later bounded update after the permanent configuration has accumulated more production history, but I am not scheduling one now.