Monitoring GitHub Actions scheduled workflows (cron) for missed and failed runs
The schedule: trigger in GitHub Actions is convenient for nightly jobs, but it has caveats that catch people out:
- Schedules run in UTC and can be delayed during periods of high load. Runs at the top of the hour are the most affected.
- In public repositories, scheduled workflows are automatically disabled after 60 days without repository activity.
- A failing run sends a notification to whoever last edited the cron schedule, which is often someone who left the team.
None of these produce an alert anyone sees. A heartbeat does.
Add a heartbeat step
Store the ping URL as a repository secret (here CRONPUP_URL). Then ping on success and on failure:
name: nightly-export
on:
schedule:
- cron: "17 3 * * *" # avoid :00, which is the most congested minute
workflow_dispatch:
jobs:
export:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: ./scripts/export.sh
- name: Heartbeat (success)
if: success()
run: curl -fsS -m 10 --retry 3 "${{ secrets.CRONPUP_URL }}"
- name: Heartbeat (failure)
if: failure()
run: curl -fsS -m 10 --retry 3 "${{ secrets.CRONPUP_URL }}/fail"
Set a generous grace period
Because scheduled runs can start late, use the same cron expression in CronPup (timezone UTC) and a grace
period of 30–60 minutes. The heartbeat then catches the real problems (disabled workflows, persistent failures) without
alerting on ordinary queue delays.
Monitor your scheduled workflows. The free plan covers 10.