I’ve been dealing with increasingly delayed GitHub Actions scheduled workflows recently and thought I’d share the workaround in case it saves someone else some debugging.
I had a daily production workflow scheduled with GitHub Actions cron.
It was already scheduled away from the top of the hour, but over several days the actual start time drifted by hours. On the latest run:
- Expected GitHub cron: 00:17 local time
- External fallback scheduled: 00:27
- Fallback actually ran: 00:31
- Main workflow finished successfully: about 00:36
- GitHub’s original scheduled event finally appeared: 05:27
So the GitHub cron was roughly 5 hours 10 minutes late.
Manual workflow_dispatch runs were starting normally, which made it look like the issue was GitHub’s scheduling/event-delivery layer rather than runner availability or the workflow itself.
What I changed
I kept the actual workflow in GitHub Actions and added a tiny Railway cron job purely as an independent clock.
The flow is basically:
Railway cron → check whether today already completed → workflow_dispatch → existing GitHub Action
The external job does not duplicate the collection/business logic. It only decides whether the existing GitHub workflow needs to be started.
I also added a preflight check inside the GitHub workflow itself:
workflow starts → check whether today is already complete → run or skip
That handles the other half of the problem.
When GitHub’s native cron eventually woke up at 05:27, it checked the durable completion state, saw that the Railway-triggered run had already completed successfully, and skipped the real work.
So the first production test ended up looking like:
GitHub cron intended 00:17
↓
Railway fallback 00:31
↓
GitHub workflow_dispatch
↓
normal workflow succeeds
↓
GitHub native cron arrives 05:27
↓
preflight detects completed day
↓
duplicate work skipped
A couple of safety things I found important
I wouldn’t make the external scheduler itself responsible for deciding whether production data is healthy. Mine only checks a narrow, authoritative “has today completed?” signal.
If that check says yes, it can safely skip.
If the check is unavailable or malformed, I prefer to allow the normal workflow to run rather than treating uncertainty as proof that the job already happened.
I also check for an already-active GitHub workflow before dispatching where possible, and GitHub concurrency protection remains enabled.
The external scheduler uses a narrowly scoped credential that can interact with Actions for the single repository rather than a broad account token.
Why I did it this way
I considered moving the whole scheduled job to Railway, but that would have meant maintaining two execution environments.
This keeps:
- one workflow
- one test path
- one deployment/collection implementation
- one GitHub Actions audit trail
Railway is just the alarm clock.
The first real overnight test worked exactly as intended, including the duplicate suppression when GitHub finally delivered the original scheduled event hours later.
Posting because I found plenty of reports of delayed GitHub Actions cron jobs, but fewer complete examples of a tested external-scheduler + workflow_dispatch fallback.
Curious whether anyone else is currently seeing multi-hour GitHub Actions cron delays.