GitHub Actions Cron Not Running: Ranked Fix Checklist

Your schedule: trigger looks correct, the cron expression validates, and yet the workflow never fires. This is one of the most-reported GitHub Actions complaints, and the cause is almost always one of seven things. Work through them in this order, top to bottom, because they are ranked by how often they are the real culprit. Each step includes a copy-paste fix and a verification command so you do not have to guess whether it worked.

First, a dispatch test harness

Before debugging the schedule, prove the workflow itself runs. Add a manual trigger alongside schedule so you can fire it on demand and read the logs:

on:
  workflow_dispatch:
  schedule:
    - cron: '0 * * * *'

Commit that, then trigger it from the CLI and watch the result:

gh workflow run my-workflow.yml
gh run list --workflow=my-workflow.yml

If the manual run succeeds but no scheduled run ever appears, the job is fine and the problem is one of the scheduling causes below. If the manual run also fails, fix that first.

The ranked causes, most common first

1. The workflow is not on the default branch

This is the number-one reason by a wide margin. GitHub only evaluates schedule triggers from the workflow file as it exists on the repository's default branch (usually main). A perfect cron on a feature branch will never fire. Per GitHub's official documentation, scheduled events run only on the default branch, using the latest commit there.

Fix: merge the file to your default branch.

git checkout main
git merge your-feature-branch
git push origin main
gh run list --workflow=my-workflow.yml

2. The 60-day inactivity auto-disable

GitHub automatically disables a scheduled workflow after 60 days with no commit activity on the repository's default branch. You typically get an email warning, but the schedule then silently stops while nothing in the YAML looks wrong. Note that only commits reliably count as activity here; creating a release tag does not always reset the clock.

Fix: re-enable it and push any commit to reset the inactivity timer.

gh workflow enable my-workflow.yml
gh workflow list --all
gh run list --workflow=my-workflow.yml

For a hands-off solution, a scheduled "keepalive" step that commits a trivial marker to the default branch before the 60-day mark keeps the trigger alive indefinitely.

3. The last cron committer lost repo access

This is the obscure one that wastes hours. A scheduled workflow is associated with the user who last modified the cron schedule. If that person loses access to the repository (left the org, token revoked, account deactivated), the schedule can quietly stop firing while manual and push triggers keep working.

Fix: have a current, active member commit a change to the workflow file so they become the owning identity.

git commit --allow-empty -m "Re-own scheduled workflow"
# or edit the cron line, then:
git push origin main
gh run list --workflow=my-workflow.yml

4. An unquoted cron string broke the YAML parse

In YAML, a value starting with * is interpreted as an alias reference, so an unquoted cron whose first field is an asterisk is a parse error. GitHub may then ignore the schedule entirely. Always wrap the expression in quotes. Note that the trap only bites when the expression starts with an asterisk (a leading number like 0 9 * * 1 happens to parse either way), so just quote everything and never think about it again.

# wrong (leading * is read as a YAML alias)
- cron: */5 * * * *
# right
- cron: '*/5 * * * *'

Validate the field layout with a dedicated cron parser and preview the next run times with a cron simulator before committing.

5. Unsupported @hourly / @daily macros

GitHub Actions accepts only standard five-field POSIX cron syntax (minute, hour, day-of-month, month, day-of-week). The convenient nicknames @hourly, @daily, @weekly, and @reboot that some cron implementations support are explicitly not valid here and will be rejected or ignored.

# invalid in GitHub Actions
- cron: '@daily'
# valid five-field equivalent (midnight UTC)
- cron: '0 0 * * *'

Note that all GitHub Actions schedules run in UTC, with no timezone option, so convert your local time accordingly. The crontab cheat sheet covers the five-field layout.

6. Forks have schedules disabled by default

When you fork a repository, GitHub disables scheduled workflows in the fork to avoid running someone else's cron jobs on your account. The YAML is identical to upstream, but nothing fires.

Fix: open the Actions tab in the fork and explicitly enable workflows, or use the CLI:

gh workflow enable my-workflow.yml
gh run list --workflow=my-workflow.yml

7. The 15-to-60-minute delay is not a bug

If your schedule did fire but minutes late, that is expected behavior, not a failure. Scheduled runs are queued on shared infrastructure and can be delayed during periods of high load. GitHub's documentation explicitly warns that the start of every hour is a high-load window, that schedules may be delayed, and that if load is high enough some queued runs may be dropped entirely. Very high-frequency schedules are the most likely to be pushed back.

Fix: stop scheduling on the hour. Offset the minute to a less popular value and avoid sub-five-minute intervals (five minutes is the practical minimum cadence).

# instead of '0 * * * *' (top of every hour, busiest slot)
- cron: '23 * * * *'

For time-critical jobs, the durable fix is to trigger the workflow externally: have a real scheduler call workflow_dispatch via the GitHub API at the exact time you need, so you no longer depend on the shared cron queue.

Confirm the fix end to end

After applying a change, do not just wait and hope. Build a clean schedule with the GitHub Actions workflow generator, push it to the default branch, and then watch for the next scheduled execution:

gh run list --workflow=my-workflow.yml --limit 20

Look for a run whose event column reads schedule. If you only ever see workflow_dispatch or push events, the schedule is still not registering and you have skipped a step above. If you want a deeper grounding in the cron field syntax itself, our cron expression cheat sheet breaks down each position with examples.

Quick triage summary

  • Nothing ever runs: check the branch (1), inactivity disable (2), and committer ownership (3).
  • YAML errors or silent ignore: quote the cron (4) and drop the macros (5).
  • Fork with no runs: enable workflows (6).
  • Runs, but late: that is the load delay (7) - offset the minute and widen the interval.

Run them in order and you will resolve almost every "scheduled workflow not firing" report without filing a support ticket.

Frequently Asked Questions

The most common reason is that the workflow file is not on the repository's default branch. GitHub only evaluates schedule triggers from the default branch, so a cron on a feature branch never fires. Merge the file to main and confirm with gh run list, looking for a schedule event.

Yes. GitHub automatically disables a scheduled workflow after 60 days with no commit activity on the default branch, usually emailing a warning first. Re-enable it with gh workflow enable and push any commit to reset the inactivity timer so the schedule resumes.

That is expected, not a bug. Scheduled runs share a queued infrastructure and can be delayed during high load, and GitHub notes the top of every hour is the busiest slot. Offset your minute field, avoid intervals shorter than five minutes, and trigger time-critical jobs externally via workflow_dispatch.

No. GitHub Actions only accepts standard five-field POSIX cron syntax. Nicknames like @daily, @hourly, and @reboot are not supported and will be ignored or rejected. Use the explicit five-field equivalent, such as 0 0 * * * for daily at midnight UTC.

YAML treats a leading asterisk as an alias reference, so a cron whose first field is * must be quoted. Write cron: '*/5 * * * *' with quotes. An unquoted value that starts with an asterisk can break the parse and cause the schedule to be ignored entirely.