Skip to main content

GitHub Actions Scheduled Workflow Not Running: The Misses That Leave No Trace

A GitHub Actions scheduled workflow not running leaves no failed run to find. The four documented causes, and how to detect a miss that logs nothing.

FLAREWARDEN
FlareWarden Team
6 min read

GitHub’s own documentation says the quiet part out loud: if load is high enough, some queued jobs may be dropped. Not delayed. Dropped. That one sentence explains a whole category of workflows that were configured correctly, sat on the right branch, and simply never ran last Tuesday — leaving behind no failed run, no red X, and no email.

The Run List Cannot Show You What Didn’t Happen

Every debugging instinct for GitHub Actions points at the Actions tab, and for a normal failure that works fine. A job that errors leaves a run, a log, and a notification.

A schedule that never fires leaves none of those things. There is no row to click, because no run was ever created. Notification settings only ever fire on a run that failed, so absence produces silence that looks exactly like success. That asymmetry is the whole problem: a GitHub Actions scheduled workflow not running is invisible to the tool you would naturally use to investigate it.

Four Documented Reasons the Schedule Doesn’t Fire

All four are in GitHub’s events reference. None of them produce an error anywhere in the UI.

  1. The workflow file isn’t on the default branch. Scheduled workflows run on the latest commit on the default branch, full stop. A schedule: trigger on a feature branch is inert. This also bites after a default-branch rename, when the file stays behind on the old branch.
  2. The repository went quiet for 60 days. In a public repository, scheduled workflows are automatically disabled when no repository activity has occurred in 60 days. Archived side projects and finished client work hit this constantly, and the disabling is silent.
  3. Top-of-the-hour load. GitHub states the schedule event can be delayed during periods of high load, that high load times include the start of every hour, and that sufficiently high load can cause queued jobs to be dropped. Their own advice is to schedule away from the top of the hour.
  4. The interval is below the floor. The shortest interval for a scheduled workflow is once every five minutes. Anything tighter isn’t honored.

Rule out the first two before touching any YAML. They cost about ninety seconds to check and account for most reports of a schedule that “stopped working” without a code change.

The Trap in Reason Three

Causes one, two, and four are static: once fixed, they stay fixed. Cause three is not. A dropped run is a probabilistic event on GitHub’s infrastructure, and no amount of correct configuration prevents it.

That matters because it kills the tempting conclusion. Once you’ve moved the cron expression off 0 * * * * and confirmed the file is on main, the workflow is correct — and it will still miss a run occasionally. Correct configuration buys you a lower miss rate, not zero misses. Any plan that ends at “I fixed the YAML” is incomplete.

Monitor the Expectation, Not the Execution

The fix is to stop asking GitHub whether the job failed and start asking whether it happened. Those are different questions, and only the second one catches a run that was never created.

A heartbeat monitor inverts the direction of the check. Instead of something polling your workflow, the workflow reports in. You register the schedule you expect, the job pings a URL when it finishes, and if a ping doesn’t arrive inside the window you were promised, that silence becomes the alert. In FlareWarden this is a cron monitor: the ping URL carries a UUID that acts as its own credential, so there’s no API key to store in repository secrets and no inbound access to grant.

Add it as the last step, after the work:

name: nightly-report
on:
  schedule:
    - cron: '37 4 * * *'   # deliberately off the hour

jobs:
  report:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Generate report
        run: ./scripts/nightly-report.sh
      - name: Signal completion
        run: curl -fsS -m 10 https://app.flarewarden.com/ping/${{ secrets.PING_UUID }}

Two details earn their keep. The ping goes last, so it can only fire when the real work succeeded, and -f makes curl exit non-zero on an HTTP error rather than quietly reporting success.

For jobs where you care about duration as well as completion, ping /start first and /complete at the end; FlareWarden calculates the elapsed time from the pair. A job that dies partway then shows up as a hung run rather than a missed one, which is a genuinely different diagnosis. Sending /fail from a failure branch turns an explicit error into an immediate alert instead of one that waits out the grace period.

Size the Grace Period Against GitHub’s Precision, Not Your Cron Expression

This is where most heartbeat setups go wrong, and the advice usually given is too aggressive. GitHub delays scheduled runs under load by design. A grace period of five minutes on an hourly job will page you for behavior GitHub documents as normal, and you’ll have rebuilt alert fatigue with extra steps.

Our position: give a scheduled GitHub Actions workflow a grace period of at least 30 minutes, and closer to an hour for anything running daily. You will find out about a genuinely missed run several hours before anyone downstream notices stale data, which is the entire point. Trading a little detection speed for alerts you still trust in six weeks is a good deal — the same argument applies when you monitor Laravel scheduled tasks or chase down a Vercel cron job not running, where the platform’s own timing precision sets the floor.

Not Every Workflow Deserves an Alert

Instrumenting all of them is how you end up ignoring the one that matters. The line we’d draw: monitor workflows that move money, publish artifacts other people consume, send customer-facing email, or write data something else reads later. Skip the linters, the label tidiers, and the stale-issue closers — their failure self-corrects on the next run and costs nothing.

A dependency-audit workflow that misses a night is fine. A nightly database backup that misses a night is a different kind of Tuesday. Cron and heartbeat monitoring is on every FlareWarden plan including the free one, so the constraint on what you watch should be judgment, not licensing.


Key Takeaways

  • A GitHub Actions scheduled workflow not running leaves no evidence — no run row, no log, no failure notification, so the Actions tab cannot tell you the schedule didn’t fire.
  • Check the branch and the 60-day rule first — scheduled workflows run only on the default branch, and public repos auto-disable them after 60 days without activity.
  • GitHub admits it drops runs under load — high load at the top of every hour can delay or discard queued scheduled jobs, so move your cron expression off :00.
  • Correct YAML doesn’t mean zero misses — configuration fixes lower the miss rate; only an external heartbeat detects the misses that remain.
  • Grace periods belong to the platform, not the schedule — 30 to 60 minutes matches GitHub’s documented imprecision and keeps the alert believable.

Want to hear about the nightly job that quietly didn’t run? Start monitoring free with FlareWarden — cron and heartbeat monitors on every plan, 15 monitors, no credit card required.