- Cron jobs
Monitoring GitHub Actions scheduled workflows (cron that GitHub can skip)
A GitHub Actions schedule can start late, get dropped under load, or be switched off after 60 quiet days. Here's how to find out, with a final step that reports every run.
Jason
Co-founder, Pingstack
GitHub Actions is a handy place for a scheduled job. There’s no server to run, and the workflow lives right next to the code. It’s also one of the easiest schedulers to lose track of. GitHub can delay or drop a scheduled run under load, it switches schedules off in quiet public repositories, and it only emails one person when a run fails. A workflow that stopped running sends no failure email at all.
How GitHub Actions schedules fail silently
The run is late or never happens. GitHub queues scheduled runs and starts them when runners are free. At busy times, especially the top of each hour, runs can start well after the cron time, and GitHub’s documentation notes that some queued runs may be dropped.
The schedule is switched off. In public repositories, GitHub disables scheduled workflows after 60 days without repository activity. The workflow file is still there. It just doesn’t run.
The schedule was never on. Schedules only run from the workflow file on
the default branch. A schedule: trigger added on a feature branch, or a
workflow whose default branch changed, never fires.
The run fails and the email goes to the wrong person. Failure emails for a scheduled workflow go to whoever last changed its cron line. That person may have left the team, or filtered out Actions emails a long time ago.
The run succeeds and does nothing. The export step exits 0 after exporting zero rows, because a token expired and the API returned an empty list.
Why the Actions tab isn’t monitoring
The Actions tab shows runs that happened. It can’t show you the run that should have happened at 02:00 and didn’t. And honestly, who opens it to check on a job that seems fine? You want a signal that has to arrive for everything to be OK. The workflow reports each run to an outside heartbeat check, and the check alerts you when that report is late or missing.
Set up GitHub Actions schedule monitoring
1. Create a heartbeat check with the workflow’s cron
Copy the expression from on.schedule.cron, for example 0 2 * * *, into a
heartbeat check. GitHub Actions schedules run in UTC unless the schedule sets
a timezone with an IANA name such as America/New_York, so give the check
the same time zone: UTC, or whatever timezone says. The
cron expression explainer shows what an
expression means and when it next runs in any time zone.
Give it a generous grace period, because GitHub starts scheduled runs late
when runners are busy. Thirty to sixty minutes works for most daily workflows.
It also helps to move the schedule off the top of the hour, say 17 2 * * *
instead of 0 2 * * *.
2. Store the ping URL as a repository secret
Anyone with the ping URL can report a run, so don’t commit it. In the
repository’s Settings › Secrets and variables › Actions, add a secret
named PINGSTACK_PING_URL. GitHub masks secret values in logs.
3. Report each run as the last step
The free, open-source Pingstack Heartbeat action does the reporting:
name: Nightly export
on:
schedule:
- cron: '17 2 * * *'
workflow_dispatch:
jobs:
export:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run the export
run: ./scripts/export.sh
- name: Report to Pingstack
if: always()
uses: pingstack-io/heartbeat-action@v1
with:
ping-url: ${{ secrets.PINGSTACK_PING_URL }}
if: always() runs the report even when an earlier step failed. A successful
run sends a heartbeat. A failed run reports the failure, so you get an alert
right away with a link to the run, without waiting for the deadline. The
action keeps the ping URL out of the logs, and workflow_dispatch adds a
Run workflow button for testing.
Prefer plain curl? The docs recipe
has a version without the action.
4. Test it
Open the Actions tab, select the workflow and choose Run workflow. When it finishes, the check should show a heartbeat and its next deadline. A manual run counts like any other, and the check then expects the next scheduled time.
Catch “succeeded but did nothing”
Report a count with each run and set warning and critical thresholds on the check, so an export of zero rows still raises an alert:
- name: Run the export
id: export
run: echo "rows=$(./scripts/export.sh --count)" >> "$GITHUB_OUTPUT"
- name: Report to Pingstack
if: always()
uses: pingstack-io/heartbeat-action@v1
with:
ping-url: ${{ secrets.PINGSTACK_PING_URL }}
value: ${{ steps.export.outputs.rows }}
Workflows with several jobs
If a workflow has several jobs that all have to succeed, report from a final
job that needs the others. A report from inside one job only proves that one
job worked. Give each scheduled workflow its own check and secret.
Common questions
How late can a GitHub Actions scheduled run be? GitHub doesn’t promise a start time. Delays of several minutes are common at busy times, and longer ones happen. If a check alerts late and then recovers on its own, make the grace period longer and leave the workflow alone.
Can a GitHub Actions schedule use my time zone? Yes. Add
timezone: "Europe/London" (any IANA name) next to cron under schedule.
Without it, the schedule runs in UTC. Either way, set the same time zone on
the check.
How often can a scheduled workflow run? The shortest interval GitHub allows is every 5 minutes. If something has to run more often, or right on time, I’d use a dedicated scheduler.
How do I stop GitHub disabling my schedule? Any repository activity resets the 60-day clock in a public repository. A disabled workflow can be turned back on from the Actions tab. A heartbeat check tells you the day it happens, so you don’t find out weeks later from the missing output.
Will the report step run in forks? Forks and pull requests from forks don’t receive repository secrets, so the step has no URL there. Schedules don’t run in forks by default either.
Try it
Pingstack’s cron job monitoring takes the workflow’s cron expression and time zone, alerts when a run is missing or late, and tracks a reported number against thresholds. Free covers 15 checks. The full GitHub Actions recipe is in the docs, and the cron monitoring guide covers the general setup. Start free and add a check to the workflow you’d least like to stop without anyone noticing.