How to Monitor GitHub Actions Scheduled Workflows
GitHub disables scheduled workflows in public repositories after 60 days of inactivity, and the schedule only runs from the default branch.
Monitoring a scheduled GitHub Actions workflow means covering the ways a schedule stops firing without the workflow ever failing. GitHub reports failed runs well and reports absent runs not at all.
A workflow that does not run produces no red X, no notification email and no entry in the Actions tab. There is nothing to look at.
The four reasons a schedule stops
The inactivity rule is the one that catches people. GitHub's documentation states that in a public repository, scheduled workflows are automatically disabled when no repository activity has occurred in 60 days. The qualifier matters and is widely dropped when the rule is repeated.
Before it happens, the Actions tab shows a notice that the workflow will be disabled soon. Nobody reads the Actions tab of a repository nobody has committed to in two months, which is the entire problem.
| Cause | Signal you get | Fix |
|---|---|---|
| 60 days inactivity (public repos) | Banner in Actions tab, sometimes an email | Push any commit, re-enable the workflow |
| Workflow file not on default branch | Nothing at all | Merge to the default branch |
| Workflow manually disabled | Greyed out in the Actions tab | gh workflow enable |
| Schedule owner lost repo access | Nothing | Re-commit the cron line as an active user |
| Platform load | Run is late or skipped | Avoid :00, widen the interval |
The default-branch rule is the second most common. A cron added on a feature branch never fires, and nothing tells you, because from GitHub's point of view there is no schedule to run.
Ping at the end of the job
Add a step at the end of the workflow. Keep it conditional on success so a failed job does not produce a healthy-looking ping.
Always keep workflow_dispatch alongside the schedule. Waiting until 03:17 to find out whether your change works is a waste of a day, and the manual trigger costs nothing.
Set the grace period wider than you would elsewhere
Scheduled workflows on shared runners are queued rather than guaranteed. Delays of several minutes are normal and longer delays happen at peak times, which for GitHub means the top of every hour.
Two practical consequences. Schedule at an offset minute such as 17 or 43 rather than 0, and set the monitor's grace period to at least 30 minutes for a daily job. A five-minute grace period on a GitHub-hosted schedule will page you for ordinary queueing.
Schedules are also evaluated in UTC, so a workflow set for local business hours drifts by an hour twice a year.
A keepalive is a workaround, not a fix
The common answer to the 60-day rule is a step that commits a trivial file to keep the repository active. It works, and it pollutes your history with meaningless commits forever.
The better answer is a heartbeat monitor with an interval that matches the schedule. If the workflow is disabled, no ping arrives, and you find out within a day instead of discovering it months later. Then you decide whether that workflow still needs to exist at all.
Related guides
For scheduled work on your own infrastructure see what is a cron job, and for the monitoring technique heartbeat monitoring.