Monitor GitHub Actions Scheduled Workflows
Updated
GitHub Actions is a convenient place for nightly builds, data syncs and cleanup scripts. But a scheduled workflow can stop without a clear signal: GitHub may delay or drop runs under load, it disables schedules in public repositories after a period of inactivity, and failure emails go to whoever last edited the cron line. This guide adds a Cuckoo check so you get an alert when a scheduled run fails or does not happen.
How Cuckoo sees the workflow
You create a check with the same schedule as the workflow. The workflow then calls:
https://cuckoo.metastic.ch/ping/YOUR-UUID/startat the beginning,https://cuckoo.metastic.ch/ping/YOUR-UUIDwhen all steps succeeded,https://cuckoo.metastic.ch/ping/YOUR-UUID/failwhen a step failed.
If neither a success nor a failure ping arrives within the schedule plus grace time, the check goes down. That covers the cases where GitHub never started the workflow.
Scheduling facts to know
From the GitHub documentation on the schedule event:
- Cron expressions run in UTC by default. Since March 2026 you can add an IANA
timezonenext tocron. - The shortest interval is every 5 minutes.
- Runs can be delayed during periods of high load, and the start of every hour is a busy time. Under heavy load some queued runs may be dropped.
- In a public repository, scheduled workflows are automatically disabled when there has been no repository activity for 60 days.
- Scheduled workflows run on the latest commit of the default branch.
These are exactly the failure modes an external check is good at catching: nothing fails, the run just does not happen.
Step 1: store the ping URL as a secret
In the repository, go to Settings → Secrets and variables → Actions → New repository secret and create
CUCKOO_URL with the value https://cuckoo.metastic.ch/ping/YOUR-UUID. With the GitHub CLI:
gh secret set CUCKOO_URL --body 'https://cuckoo.metastic.ch/ping/YOUR-UUID'
Secrets are masked in logs and are not passed to workflows triggered from forks.
Step 2: the workflow
name: nightly-sync
on:
schedule:
- cron: "17 3 * * *"
timezone: "Europe/Zurich" # optional; omit for UTC
workflow_dispatch: {}
jobs:
sync:
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- name: Cuckoo start
if: github.event_name == 'schedule'
run: curl -fsS -m 10 --retry 5 -o /dev/null "${{ secrets.CUCKOO_URL }}/start" || true
- uses: actions/checkout@v4
- name: Run sync
run: ./scripts/sync.sh
- name: Cuckoo success
if: success() && github.event_name == 'schedule'
run: curl -fsS -m 10 --retry 5 -o /dev/null "${{ secrets.CUCKOO_URL }}" || true
- name: Cuckoo failure
if: failure() && github.event_name == 'schedule'
run: curl -fsS -m 10 --retry 5 -o /dev/null "${{ secrets.CUCKOO_URL }}/fail" || true
How the conditions work:
success()is true only if every previous step succeeded. It is also the default when noifis set.failure()is true if any previous step failed. It is not true for a cancelled run; usecancelled()if you want to report those as well.github.event_name == 'schedule'keeps manualworkflow_dispatchtest runs from sending pings. Drop it if you want manual runs to count too.
The minute 17 is deliberate. Avoiding 0 moves the run away from the top of the hour, when the docs say
delays are most likely.
Workflows with several jobs
If the work is spread across jobs, add a final job that depends on all of them and always runs:
report:
needs: [build, sync, publish]
if: always() && github.event_name == 'schedule'
runs-on: ubuntu-latest
steps:
- name: Report result
env:
HB: ${{ secrets.CUCKOO_URL }}
FAILED: ${{ contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled') }}
run: |
if [ "$FAILED" = "true" ]; then
curl -fsS -m 10 --retry 5 -o /dev/null "$HB/fail" || true
else
curl -fsS -m 10 --retry 5 -o /dev/null "$HB" || true
fi
Schedule and grace advice
Use the same cron expression in Cuckoo. If you set no timezone in the workflow, choose UTC for the
check; if you did, use the same IANA name. Because scheduled runs can start late, set a generous grace time:
the job's normal runtime plus 30 to 60 minutes is a sensible starting point for a daily job. Set max runtime
to slightly more than timeout-minutes.
Troubleshooting
- Check went down, no run in the Actions tab: look for a banner saying the workflow was disabled. Re-enable
it on the workflow page or with
gh workflow enable nightly-sync. - Schedule ignored after editing: the
scheduletrigger only applies on the default branch. Changes on a feature branch have no effect until merged. - Pings fail with an empty URL: the secret name does not match, or the run was triggered from a fork, where secrets are not available.
FAQ
Why did my scheduled workflow run late or not at all?
GitHub documents that schedule events can be delayed during high load, and queued runs may be dropped. An
external check with a grace time tells you when a delay turns into a missed run.
Do I still need Cuckoo if GitHub emails me about failures?
GitHub notifies about failed runs, but a run that never started produces no failure notification, for example after the 60-day inactivity disable in public repositories. Cuckoo alerts on missing pings.
Can I attach the job log to the failure ping?
Yes. Write output to a file with tee, then POST it: tail -c 10000 out.log | curl -fsS -m 10 --data-binary @- "$HB/fail". The first 10 KB are stored with the ping and shown in the dashboard.
Related guides
- Monitor GitLab CI Scheduled PipelinesMonitor GitLab pipeline schedules with Cuckoo: rules on CI_PIPELINE_SOURCE, an after_script that reports CI_JOB_STATUS, and a masked CI/CD variable.
- Cron Job Start and Fail SignalsSend /start, success and /fail pings from cron jobs to measure run time and catch hung jobs. Includes a reusable bash wrapper script with trap and exit codes.
- Cron Schedule, Time Zone and Grace PeriodPick the right monitoring schedule for a cron job: period or cron expression, IANA time zones, how cron handles daylight saving time, and sizing grace time.
- Dead Man's Switch Monitoring ExplainedWhat dead man's switch monitoring is, how push-based heartbeat checks differ from pull-based uptime monitoring, and which jobs and systems benefit from each.