Monitoring scheduled GitHub Actions workflows
The schedule trigger turns GitHub Actions into a free cron service, and plenty of teams run nightly syncs, reports and cleanups on it. It has quirks that are easy to miss, though, and a run that never happens doesn’t show up as a failure anywhere.
What to know about scheduled workflows
- Times are in UTC.
0 9 * * *runs at 09:00 UTC, not in your time zone. - Runs can be late or dropped. GitHub’s documentation says scheduled runs can be delayed when Actions is under heavy load, with the start of every hour among the busiest times, and that under enough load some queued jobs may be dropped.
- They switch off on quiet repositories. In a public repository, scheduled workflows are disabled automatically after 60 days without repository activity.
- They use the default branch. The workflow file on the default branch decides the schedule; changes on other branches have no effect.
- The shortest interval is 5 minutes.
Pick an odd minute such as 17 instead of 0 to stay away from the busiest moment of the hour.
A workflow that reports
Save the check’s ping URL as a repository secret called HEARTCRON_URL (Settings → Secrets and variables → Actions), then report the start and the result:
name: Nightly sync
on:
schedule:
- cron: "17 3 * * *" # 03:17 UTC
workflow_dispatch:
jobs:
sync:
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v4
- name: Report the start
run: curl -fsS -m 10 --retry 3 -o /dev/null "${{ secrets.HEARTCRON_URL }}/start" || true
- name: Sync
run: ./scripts/sync.sh
- name: Report the result
if: always()
run: curl -fsS -m 10 --retry 3 -o /dev/null "${{ secrets.HEARTCRON_URL }}/${{ job.status == 'success' && '0' || 'fail' }}"
if: always()runs the last step even when the sync fails, and sends/0on success or/failon failure.timeout-minutesstops a stuck run; together with/startthat shows up as a failure instead of a mystery.workflow_dispatchlets you start the workflow by hand to test the whole chain.|| trueon the start ping keeps the workflow going if the monitoring service is briefly unreachable.
Set up the check
Use the same expression, 17 3 * * *, with the time zone set to UTC. Be generous with the grace period, because runs are often late: 30 to 60 minutes is sensible for a nightly job. You get an alert when the run fails, when it doesn’t finish, and when it doesn’t happen at all, including the day GitHub disables the schedule on a quiet repository.
Many scheduled workflows
Give each workflow its own check, named after the workflow file, and keep their URLs in separate secrets. The Heartcron dashboard then shows at a glance which schedules ran today and how long each one took.