To monitor a GitHub Actions scheduled workflow, make its last step ping a heartbeat URL and alert when that ping does not arrive. GitHub notifies you when a run fails, but not when a run is never created: a workflow disabled after 60 days of inactivity, a schedule dropped under load, or a file that never reached the default branch all look like nothing at all.

I'm Léo, I build Hyperping. In this guide I cover how scheduled workflows go missing, numbers I measured on public repositories in October 2026, the native ways to check them, and the setup I use with Hyperping healthchecks.

Key takeaways

  • GitHub documents that the schedule event "can be delayed during periods of high loads" and that "some queued jobs may be dropped". On four public workflows scheduled every 15 minutes, only 64% of slots got a run over 7 days, with a median start delay of 7.4 minutes.
  • In a public repository, scheduled workflows are disabled after 60 days without repository activity. The runs stop and nobody gets a failure notification.
  • Since 19 March 2026, a timezone key next to cron sets an IANA timezone. Without it, schedules run in UTC.
  • Ping /start in the first step and the success URL in the last step. The last step only runs when every step before it succeeded.
  • GitHub Actions is fine for daily and hourly jobs with a generous grace period. If every 15 minute slot matters, move that job to a scheduler you control.

How scheduled workflows fail silently

Runs start late, and some never start

The schedule event docs say it plainly: "The schedule event can be delayed during periods of high loads of GitHub Actions workflow runs. High load times include the start of every hour. If the load is sufficiently high enough, some queued jobs may be dropped."

I wanted numbers, so on 8 October 2026 I pulled every scheduled run created between 1 and 8 October 2026 for four active public workflows with cron: '*/15 * * * *' and no timezone key (opentofu/registry-ui, csfloat/cs-files, InjectiveLabs/injective-lists and qntx/erc8004), using the GitHub REST API:

Measure, 4 workflows, 7 days Value
Expected runs per workflow per day 96
Runs actually created per workflow per day 55.6 to 63.9
Share of 15 minute slots with a run 64%
Median start delay after the slot 7.4 minutes
Gaps of 25 minutes or more between two runs 25.7%
Longest gap between two runs 137 minutes

The worst stretch hit all four repositories on the same day: on 4 October 2026, from about 05:00 to 14:30 UTC, each of them got a run only every two hours or so. That points to GitHub's side, not to the workflows. None of the missing runs shows up as failed, cancelled or skipped: there is no run at all.

One limit of the method: I measured each delay from the 15 minute slot just before the run, so a run more than 15 minutes late counts as a short delay on the next slot. The share of slots with a run is the more reliable number.

Daily and weekly schedules are delayed too. GitHub's own actions/runner-images runs CodeQL with cron: '32 4 * * 0'. Over the last eight Sundays it started between 04:40 and 04:46 UTC, except on 4 October 2026, when it started at 06:11, 1 hour 39 minutes late, on the same morning as the gaps above. Its Monday 0 12 * * 1 check starts 3 to 5 minutes late every week.

Public repositories disable schedules after 60 days

"In a public repository, scheduled workflows are automatically disabled when no repository activity has occurred in 60 days." Libraries that are stable, data scrapers and status repos are exactly the projects where nobody commits for two months. The workflow moves to disabled_inactivity and runs stop.

You can re-enable it from the Actions tab (Enable workflow) or with gh workflow enable nightly-backup.yml. The docs also say a commit that changes the cron schedule, by someone with write access, reactivates it.

The schedule only runs from the default branch

"Scheduled workflows will only run on the default branch", on its latest commit. A workflow added on a feature branch, or a cron change merged into develop when the default branch is main, never runs. When a public repository is forked, its scheduled workflows are disabled by default in the fork.

Times are UTC unless you set timezone

Until March 2026, every schedule was UTC, and 0 9 * * 1-5 moved by an hour twice a year for anyone outside UTC. The March 2026 changelog added a timezone key:

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

On the spring DST change, "scheduled workflows in skipped hours advance to the next valid time", so a 2:30 AM schedule runs at 3:00 AM. The docs say nothing about the repeated hour in the fall, so I keep important jobs out of the 1:00 to 3:00 window. Also note that the @daily and @hourly shortcuts are not supported, and the shortest interval is 5 minutes.

Failure notifications go to one person

"Notifications for scheduled workflows are sent to the user who last modified the cron syntax in the workflow file." If that person changed teams or filters GitHub email, failed runs reach nobody. And skipped or dropped runs produce no notification for anyone.

How to check scheduled workflows with GitHub's tools

# Last scheduled runs of one workflow
gh run list --workflow nightly-backup.yml --event schedule --limit 10

# Is the workflow still active? active, disabled_inactivity, disabled_manually...
gh api repos/OWNER/REPO/actions/workflows/nightly-backup.yml --jq .state

# Re-enable after the 60 day rule kicked in
gh workflow enable nightly-backup.yml

The Actions tab shows the same runs. All of this needs someone to look. gh run list lists the runs that exist, so a dropped run is only visible as a gap in the timestamps.

How to monitor GitHub Actions scheduled workflows with Hyperping

A Hyperping healthcheck is a secret URL your workflow calls when it succeeds. If the call does not arrive by the scheduled time plus a grace period, Hyperping opens an incident and alerts you, then resolves it on the next successful ping. Healthchecks are included on every plan, Free included.

1. Create a healthcheck with the workflow's schedule

In Hyperping, open Healthchecks, click Create healthcheck, and pick Cron. Copy the workflow's cron value into Cron expression, and choose the timezone value in Timezone, or UTC when the workflow has no timezone key.

The cron expression generator shows the next run times of an expression, for example every weekday or every hour.

2. Store the ping URL as a repository secret

In the repository, go to Settings, Secrets and variables, Actions, and add HYPERPING_BACKUP_URL with the healthcheck URL (https://hc.hyperping.io/tok_...). Anyone with the URL can send pings, so keep it out of the workflow file.

3. Ping /start first and the success URL last

name: Nightly backup

on:
  schedule:
    - cron: "17 2 * * *"
      timezone: "Europe/Paris"
  workflow_dispatch:

jobs:
  backup:
    runs-on: ubuntu-latest
    timeout-minutes: 30
    env:
      HC_URL: ${{ secrets.HYPERPING_BACKUP_URL }}
    steps:
      - name: Signal start to Hyperping
        run: curl -fsS -m 10 --retry 3 -o /dev/null "$HC_URL/start" || true

      - uses: actions/checkout@v7

      - name: Run backup
        run: ./scripts/backup.sh

      - name: Ping Hyperping on success
        run: curl -fsS -m 10 --retry 3 -o /dev/null "$HC_URL" || echo "::warning::Hyperping ping failed"

A few choices in there:

  • The last step has no if:. GitHub applies success() by default, so it only runs when every earlier step succeeded. Do not use if: always() on it, or failed backups will look healthy.
  • || true on the /start step keeps a network error from failing the backup. The success step turns a failed ping into a warning annotation instead of a red run.
  • timeout-minutes: 30 replaces the default of 360 minutes. A hung job is cancelled, sends no ping, and you get the alert hours sooner.
  • Minute 17 instead of 0: GitHub names the start of every hour as a high load time and recommends scheduling at a different time of the hour.
  • workflow_dispatch lets you run it by hand to test the ping. A manual success also counts as a ping.

The file passes actionlint 1.7.12, timezone key included. curl is installed on the ubuntu-latest and macos-latest images. On windows-latest, add shell: bash to these steps.

4. Give the grace period room for GitHub's delays

In cron mode, Hyperping expects the success ping by the scheduled time plus the grace period. On GitHub Actions, that window has to cover the job's run time and GitHub's start delay, which I measured anywhere from a few minutes to more than two hours.

For a daily or weekly workflow, I start with a grace period of 2 hours plus the job's usual duration. A late start then stays quiet, and a disabled workflow, a dropped run or a failed backup still alerts the same morning.

For workflows every 15 or 30 minutes, an alert per missing slot would fire several times a day, given the 64% figure above. Use simple mode instead, for example every 1 hour with a 90 minute grace period: you are told when the schedule stops for real, not when GitHub skips one slot. If each slot matters, run that job from a scheduler you control, such as a systemd timer or a Kubernetes CronJob.

5. Route the alerts

A missed ping alerts every channel connected to the project at once: email and SMS to every member, Slack, Discord, Telegram, PagerDuty and Opsgenie. That fixes the "one person gets the email" problem, since the whole project hears about it. Healthchecks do not use escalation policies or on-call schedules, so route them to PagerDuty or Opsgenie when a missed run has to page whoever is on call.

Test the setup

Run the workflow from the Actions tab with Run workflow and check that both pings appear in the healthcheck's last pings, with a curl user agent. Then make backup.sh exit 1 and run it again: the last step is skipped, and the alert arrives once the grace period runs out. The next successful run closes the incident.

The same approach works for the Laravel scheduler and Windows Task Scheduler. If you are choosing a heartbeat tool, start with the best cron job monitoring tools. For jobs scheduled on other platforms, see Vercel cron jobs and Cloudflare Workers cron triggers, and scheduled AI agents if a workflow runs an agent.