GitHub Actions Scheduled Workflows Are Unreliable

A note from the founder. Need your GitHub Actions to actually fire on time? I'm looking for a small group of early users to try Runhooks and share honest feedback. Early adopters get upgraded plans for free.

You add a schedule: trigger to a workflow, set it to */5 * * * *, and expect it to run every five minutes. Then you check the run history and find gaps: a run at 09:00, the next at 09:11, nothing at 09:15, one at 09:22. Your "every 5 minutes" job is running whenever GitHub gets around to it.

This isn't a bug in your cron expression. It's how the GitHub Actions schedule event works — and once you understand why, the fix is straightforward. This guide explains the failure modes and shows how to trigger workflows on time using an external scheduler.

Why GitHub Actions Schedules Are Unreliable

The schedule event runs on shared, best-effort infrastructure. GitHub is explicit that scheduled workflows are not guaranteed to run at the exact time you specify. Three distinct problems stack up:

1. Delays under load

Scheduled runs are queued alongside everyone else's. Load is heaviest at common cron times — the top of the hour, */5 boundaries — precisely when you scheduled yours. During those spikes, GitHub delays scheduled runs, sometimes by 5–20 minutes. A tight schedule becomes loose and unpredictable.

2. Dropped runs

Delay is one thing; disappearance is another. Under heavy enough load, a scheduled run can be skipped entirely — no run, no error, no notification. If that run was your hourly billing sync or data export, you simply lost it, and nothing told you.

3. Auto-disabling on inactive repos

For public repositories, GitHub automatically disables scheduled workflows after 60 days without repository activity. A job that's supposed to run untouched for months quietly stops until someone pushes a commit or re-enables it by hand. This is one of the most surprising silent failures in all of GitHub Actions.

The common thread: the schedule event optimizes GitHub's infrastructure costs, not your timing guarantees. For CI-adjacent chores where "roughly hourly" is fine, that's an acceptable trade. For anything time-sensitive, it isn't.

The Fix: Trigger the Workflow Externally

The insight is the same one that fixes scheduling on Vercel or Cloudflare: your workflow doesn't need GitHub's scheduler. GitHub exposes an API event — repository_dispatch — that lets any authenticated HTTP request start a workflow. Move the timing to a reliable external scheduler and let GitHub do what it's good at: running the job.

Step 1: Add a dispatch trigger to your workflow

Replace (or supplement) the schedule trigger with repository_dispatch:

name: Hourly Sync
on:
  repository_dispatch:
    types: [scheduled-sync]
  workflow_dispatch: {}   # also allows manual runs from the UI

jobs:
  sync:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: ./scripts/sync.sh

workflow_dispatch lets you trigger it manually for testing; repository_dispatch is what the external scheduler will call.

Step 2: Trigger it with an authenticated API call

GitHub's REST API starts the workflow when you POST to the dispatches endpoint with a fine-grained token that has Actions: write (or Contents: write) permission:

curl -X POST \
  -H "Accept: application/vnd.github+json" \
  -H "Authorization: Bearer $GITHUB_TOKEN" \
  https://api.github.com/repos/<owner>/<repo>/dispatches \
  -d '{"event_type":"scheduled-sync"}'

The workflow starts within seconds of the call — no queue, no top-of-the-hour lottery. Whatever schedule you trigger this on is the schedule the workflow actually runs on.

Step 3: Put a reliable scheduler in front of it

Now the only question is what makes that HTTP call on time. A cron job on your laptop won't cut it — that just moves the reliability problem somewhere worse. You want a scheduler that fires precisely, retries if the GitHub API hiccups, and logs every attempt.

How Runhooks Triggers GitHub Actions Reliably

Runhooks is a scheduled HTTP execution service, and triggering a GitHub workflow is just an HTTP job:

  1. Create a job — name it "Trigger hourly sync workflow."
  2. Set the URLhttps://api.github.com/repos/<owner>/<repo>/dispatches.
  3. Set the methodPOST, with body {"event_type":"scheduled-sync"}.
  4. Add headersAuthorization: Bearer <token> and Accept: application/vnd.github+json.
  5. Set the schedule — any cron expression; it fires at that time, not "sometime after."

What you get that GitHub's schedule event doesn't provide:

  • Accurate timing — the workflow is triggered when you asked, not when the queue clears.
  • Automatic retries — if the GitHub API returns a transient error, Runhooks retries instead of silently losing the run.
  • Execution logs — every trigger recorded with HTTP status and response, so a failed dispatch is visible.
  • No auto-disabling — an external trigger keeps firing regardless of repo activity.

This is the same pattern we use to work around scheduling limits on other platforms — see scheduled HTTP requests vs. cron jobs for the broader principle, and why cron jobs fail in production for the failure modes this avoids.

When the Built-in Schedule Is Fine

To be fair: if your workflow is a low-stakes chore — stale-issue cleanup, a nightly cache warm, a weekly report where ±15 minutes doesn't matter — the built-in schedule trigger is simpler and perfectly adequate. Reach for an external trigger when timing accuracy, guaranteed execution, or long-term unattended running actually matter.

Get Started

GitHub Actions is excellent at running workflows and mediocre at scheduling them on time. Separate the two:

  1. Add repository_dispatch to the workflows that need reliable timing.
  2. Try Runhooks free and trigger them on an exact schedule with retries and logs.
  3. Build and preview your cron expression with the cron visualizer.

Frequently Asked Questions

Why is my GitHub Actions scheduled workflow not running on time?

GitHub runs scheduled workflows on a best-effort basis on shared infrastructure. During periods of high load — often near the top of the hour — scheduled runs are queued and can be delayed by several minutes or, occasionally, dropped entirely. The schedule event is not a guaranteed real-time trigger, so a 5-minute cron can effectively become "every 5–15 minutes, sometimes."

Does GitHub Actions disable scheduled workflows on inactive repos?

Yes. GitHub automatically disables scheduled workflows in public repositories after 60 days of no repository activity. The workflow stops firing until someone pushes a commit or manually re-enables it. For a scheduled job that's meant to run untouched for months, this is a common and silent failure.

How do I make GitHub Actions run on a reliable schedule?

Instead of relying on the schedule trigger, expose the workflow to an external scheduler using the repository_dispatch or workflow_dispatch event. An external scheduler like Runhooks calls the GitHub API at the exact time you want, which triggers the workflow immediately with proper timing, retries if the call fails, and logs of every attempt.

What is the minimum interval for GitHub Actions cron?

The shortest interval GitHub documents for the schedule event is every 5 minutes (*/5 * * * *). In practice, delays under load mean the real gap between runs is often longer and irregular. If you need tight, dependable sub-hourly timing, an external trigger via repository_dispatch is more reliable than the built-in schedule.

Can I trigger a GitHub Actions workflow with an HTTP request?

Yes. Add a repository_dispatch (or workflow_dispatch) trigger to your workflow, then send an authenticated POST to the GitHub REST API endpoint for that event. Any scheduler that can make an HTTP request — including Runhooks — can fire the workflow on demand, on any schedule, with retries and logging the built-in cron doesn't provide.

Read next: Scheduled HTTP Requests vs. Cron Jobs · Why Cron Jobs Fail in Production · How to Monitor Cron Jobs