CronCanary DocsPricingOpen app
← Integration guides

Monitor a GitHub Actions scheduled workflow

GitHub silently disables scheduled workflows on inactive repos and skips runs under load. A dead man's switch catches it from outside GitHub entirely.

Why scheduled workflows fail silently

GitHub Actions' schedule: trigger has a documented, easy-to-forget behavior: scheduled workflows are automatically disabled after 60 days of no repository activity — no commits, no PRs, nothing. If a repo just quietly does its job and nobody pushes to it, the cron simply stops, with a banner buried in the Actions tab that almost no one checks. Beyond that, GitHub explicitly documents that schedule events are best-effort: during periods of high load across GitHub's infrastructure, scheduled runs can be delayed by many minutes or dropped entirely for that interval, with no notification to the repo. A workflow can also be silently skipped if Actions billing is disabled, the workflow file has a YAML syntax error that only breaks the schedule trigger, or the schedule is defined on a non-default branch (GitHub only reads schedule: triggers from the default branch, a frequent surprise after a rename). None of these show up as a failed run in your history — they show up as no run at all, which is exactly the gap a ping-based check is built to catch.

Add a ping step

Store your check URL as a repo secret (CRONCANARY_URL), then ping at the end of the job — only if every previous step succeeded:

name: nightly on: schedule: - cron: "0 4 * * *" jobs: run: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - run: ./scripts/nightly.sh # ping only if all previous steps succeeded - run: curl -fsS "$CRONCANARY_URL" env: CRONCANARY_URL: ${{ secrets.CRONCANARY_URL }}

Report failures explicitly

Add an if: failure() step so a broken run alerts immediately instead of waiting out the grace period:

- name: report failure if: failure() run: curl -fsS "${{ secrets.CRONCANARY_URL }}/fail"

Catch the run that never started at all

The failure step above only fires once the workflow has already been triggered. It can't help with the 60-day auto-disable, a mistyped cron expression, or a workflow file on the wrong branch — because in each of those cases GitHub never starts a run to report from. That's precisely why the check itself must be a scheduled expectation, independent of the workflow: set the check's Cron schedule to match on.schedule.cron, and a grace period generous enough to absorb GitHub's own delay under load (start with 15–30 minutes for anything time-sensitive). If the ping is late, the alert fires whether the cause was inside the workflow or GitHub silently never running it.


Related guides


Add a live status badge to your README

Every check has a public SVG badge that shows its live status (updates within ~1 minute). Paste this into any README — it doubles as a heartbeat anyone on the team can see:

[![CronCanary](https://croncanary.fluxath.app/badge/<your-check-id>.svg)](https://croncanary.fluxath.app)

Copy the exact markdown from your check's detail page. Add ?label=your-text to customize the left label.


Ready to wire this up? Create a free check — 20 checks, all alert channels, no card.