Skip to content
LastPing
GUIDE · GITHUB ACTIONS · SCHEDULE NOT RUNNING

GitHub Actions cron not running? Nine causes, and how to hear about the next one

A workflow with a schedule: trigger that never fires produces nothing: no failed run, no email, no red badge. GitHub's documentation names most of the reasons, and each one has a quick check. Work through them below, then point GitHub's own workflow_run webhook at LastPing, so a scheduled run that does not arrive opens an incident. No YAML changes.

Your assistant can do this for you

Find out why my scheduled GitHub Actions workflow is not running, and monitor it with LastPing

GitHub's documented behaviour: "Scheduled workflows will only run on the default branch." And at busy times: "If the load is sufficiently high enough, some queued jobs may be dropped." (GitHub Actions documentation, events that trigger workflows.) A run that never starts sends no notification.
On this page
  1. Check these first: two commands
  2. Nine reasons a scheduled workflow does not run
  3. Get alerted when a scheduled run does not arrive
  4. What this catches
  5. Questions people ask

Check these first: two commands

Most cases are settled by two questions: is the workflow enabled, and has the schedule ever fired?

# Every workflow with its state: active, disabled_inactivity,
# disabled_manually or disabled_fork
gh api repos/OWNER/REPO/actions/workflows \
  --jq '.workflows[] | [.name, .path, .state] | @tsv'

# Runs the schedule itself started (add -a for a disabled workflow)
gh run list --workflow nightly.yml --event schedule --limit 10

A state other than active answers the question at once. An active workflow with no schedule runs at all points to the branch, the syntax or the timezone. Runs that exist but arrive late, or with gaps, point to load.

Nine reasons a scheduled workflow does not run

In the order worth checking. Each quotes GitHub's own documentation where it says something exact.

The workflow file is not on the default branch

GitHub's documentation is explicit: "Scheduled workflows will only run on the default branch", and they run on the latest commit there. A schedule: added on a feature branch does nothing until it is merged. If the repository's default branch changed, check that the current version of the file is on the new one.

gh repo view --json defaultBranchRef --jq .defaultBranchRef.name

GitHub disabled it after 60 days without activity

In a public repository, GitHub disables scheduled workflows when no repository activity has occurred in 60 days. The state reads disabled_inactivity. Re-enable it with gh workflow enable or the Enable workflow button. The disabled scheduled workflow guide covers this case in full.

Someone disabled it by hand

Disable workflow in the Actions tab stops every trigger without deleting the file, so the YAML still looks right. The state reads disabled_manually. Enable it the same way.

It is running in a fork

GitHub's documentation says: "When a public repository is forked, scheduled workflows are disabled by default." The state reads disabled_fork. Enable the workflow in the fork's Actions tab if you want it to run there.

It runs late at busy times

"The schedule event can be delayed during periods of high loads of GitHub Actions workflow runs", and GitHub names the start of every hour as a high load time. Its own advice is to schedule the workflow at a different time of the hour: 17 3 * * * rather than 0 3 * * *.

The run was dropped

The same documentation goes further: "If the load is sufficiently high enough, some queued jobs may be dropped." A dropped run leaves nothing in the run history to look at. Only a check outside GitHub that expects the run on schedule notices it.

The interval is shorter than five minutes

"The shortest interval you can run scheduled workflows is once every 5 minutes." A schedule that asks for every minute or every two minutes will not run that often.

The schedule is in UTC, not your time

Scheduled workflows run in UTC by default, so 0 9 * * * means 09:00 UTC. A schedule entry can set an optional timezone key to an IANA name. For a zone with daylight saving, GitHub documents that a run in the hour skipped in spring advances to the next valid time: 2:30 AM becomes 3:00 AM. Paste the expression into the cron expression explainer, pick GitHub Actions and a timezone, and it lists the next runs.

on:
  schedule:
    - cron: '30 5 * * 1-5'
      timezone: "America/New_York"

The syntax is not what GitHub accepts

GitHub uses POSIX cron syntax with five fields, and it does not support the non-standard shortcuts @yearly, @monthly, @weekly, @daily, @hourly and @reboot. Day of week runs from 0 to 6, or SUN to SAT. Quote the expression: in YAML a plain value that starts with * is read as an alias, and the file does not parse.

Get alerted when a scheduled run does not arrive

GitHub sends nothing for a run that never starts, and scheduled-run notifications go to the user who last modified the cron syntax in the workflow file. A curl step inside the workflow cannot help either: if the run never starts, the step never runs. LastPing listens to GitHub's own workflow_run webhook instead and expects it on your schedule. No YAML changes, no GitHub App.

Create a CI monitor with the workflow's schedule

In the console, create a monitor, choose CI pipeline and pick GitHub Actions. Give it the same cron expression as the workflow's schedule: entry, in UTC, or in the IANA zone the entry names with timezone:. LastPing reads the expression with Linux cron rules. Set a grace window longer than the delays you see at busy times, so a late run is not reported as missed. Copy the webhook URL and the one-time secret.

# Workflow: cron: '17 3 * * *' (03:17 UTC every day)
# LastPing: schedule = "17 3 * * *", timezone = UTC, grace = 30 min
https://ping.lastping.dev/ci/github/<check-id>

Register the webhook in the repository

On GitHub, open Settings → Webhooks → Add webhook. Paste the webhook URL as the Payload URL, set the content type to application/json, paste the secret, and under individual events tick Workflow runs and Workflow jobs. Workflow jobs is what supplies the failing step name in alerts. LastPing checks each delivery's HMAC-SHA256 signature against your secret.

Branch and workflow filters on the monitor let it count only the runs you care about, for example the workflow named nightly-backup on main. A filter only counts exact matches.

Confirm with one manual run

Run the workflow once by hand (Actions → your workflow → Run workflow, which needs a workflow_dispatch trigger). The run appears in the LastPing console within seconds. From then on, a scheduled run that does not arrive inside its window opens an incident.

What this catches

One webhook covers the run that never came and the run that went wrong.

  • Never started: disabled for inactivity, disabled by hand, moved off the default branch or dropped under load. No run arrives in the window, so an incident opens.
  • Late beyond the grace window: a delay longer than the grace you set is reported, so you can move the schedule off the busy minute.
  • Failed: the run arrives with a failure conclusion and the incident links to it, naming the failing step.
  • Hung: the run started but never completed. GitHub-hosted jobs can run for up to 6 hours before GitHub stops them, and jobs on runners you host yourself for up to 5 days. The incident opens when the run overruns its window, not when GitHub stops it.
  • Recovery: the next successful run closes the incident by itself.

Alerts go to the destinations you choose: email, Slack, Discord, Telegram, Microsoft Teams, Google Chat, ntfy, Pushover or a webhook. More on scheduled workflows in the scheduled workflows guide and the GitHub Actions guide.

Questions people ask

Front-loaded answers: the most important fact first.

  • Why is my GitHub Actions cron not running?

    Check in this order: the workflow file must be on the default branch; in a public repository GitHub disables schedules after 60 days without repository activity; a workflow can be disabled by hand, and in a fork schedules are disabled by default; and runs can be delayed or dropped at busy times. The schedule is in UTC unless the entry sets a timezone, and the shortest interval is once every 5 minutes.

  • Why does my scheduled workflow run late?

    GitHub documents that the schedule event can be delayed during periods of high load, and that high load times include the start of every hour. Schedule the workflow at another minute of the hour, such as 17 instead of 0, and give your monitor a grace window that covers the delay.

  • Can GitHub skip a scheduled run entirely?

    Yes. GitHub's documentation says that if the load is high enough, some queued jobs may be dropped. A dropped run leaves nothing in the run history, so only a check outside GitHub that expects the run on schedule can notice it.

  • Does a schedule on a feature branch run?

    No. Scheduled workflows only run on the default branch, on the latest commit there. Merge the workflow file to the default branch; until then the schedule does nothing.

  • What timezone does a GitHub Actions schedule use?

    UTC by default. A schedule entry can set an optional timezone key to an IANA name such as America/New_York. In a zone with daylight saving, a run in the hour skipped in spring advances to the next valid time, so 2:30 AM becomes 3:00 AM. The cron expression explainer lists the next runs in any timezone.

  • How do I get alerted when a scheduled workflow does not run?

    GitHub sends nothing for a run that never starts, and a curl step inside the workflow cannot run either. Point the repository's workflow_run webhook at a LastPing CI monitor with the same cron schedule and a grace window: a run that does not arrive in time opens an incident, and so does a failed or hung run. No YAML changes.

  • Is this monitoring free?

    Yes. LastPing is free for individuals with no monitor cap. See how it compares to Healthchecks.io and Cronitor.

FREE FOR INDIVIDUALS · FULLY HOSTED

The run that never started is the one nobody hears about.

One webhook now beats finding the gap in the run history weeks later. First alert wired up in about two minutes, and LastPing monitors itself the same way.