GitHub Actions scheduled workflow not running
Two causes cover most cases. In a public repository, GitHub turns off scheduled workflows after 60 days without repository activity. And under load, scheduled runs start late or are dropped. Neither produces an error. This guide shows how to check, then adds three pings so you hear about it when a run starts, finishes, fails, or never happens.
You need: a Lateping account, a repo with a scheduled workflow, about 5 minutes.
Why scheduled workflows stop
A workflow with an on: schedule trigger depends on GitHub deciding to start it. When GitHub doesn’t, there’s nothing in the Actions tab to look at.
Check whether it happened to you
List your workflows with the GitHub CLI. A workflow turned off by the 60-day rule shows the state disabled_inactivity.
gh workflow list --all
To turn it back on, open the workflow in the repo’s Actions tab and choose Enable workflow, or run gh workflow enable <workflow-file>. It will be disabled again after another 60 quiet days, so you also want an alert when it happens.
Also check the basics:
- Scheduled workflows run only from the default branch.
- The shortest interval is every 5 minutes.
- The cron expression is read in UTC.
Set it up
Create a check
In Lateping, create a check and set its schedule to the same cron expression as your workflow. Set the timezone to
UTC, since that’s what GitHub uses. Give it a grace period longer than the job usually takes, plus room for queue delays. 30 minutes is a good start.Copy the check’s ping URL. It looks like
https://lateping.com/p/<check-id>.Save the ping URL as a secret
Anyone with the URL can ping your check, so keep it out of the repo. In GitHub, go to Settings → Secrets and variables → Actions, and add a secret named
LATEPING_URL. Or use the GitHub CLI:gh secret set LATEPING_URL \ --body "https://lateping.com/p/<check-id>"Add the pings to your workflow
Ping
/startfirst, then the plain URL when the job succeeds. The last step runs only if an earlier step failed, and calls/failso we alert you right away instead of waiting for the grace period.name: Nightly backup on: schedule: - cron: "17 2 * * *" # 02:17 UTC workflow_dispatch: jobs: backup: runs-on: ubuntu-latest steps: - name: Ping start run: curl -fsS -m 10 --retry 3 "${{ secrets.LATEPING_URL }}/start" - uses: actions/checkout@v4 - name: Run backup run: ./scripts/backup.sh - name: Ping success run: curl -fsS -m 10 --retry 3 "${{ secrets.LATEPING_URL }}" - name: Ping failure if: failure() run: curl -fsS -m 10 --retry 3 "${{ secrets.LATEPING_URL }}/fail"17 2 * * *runs at 02:17 UTC. Picking a minute other than:00avoids the busiest queue.-m 10 --retry 3stops a slow network call from hanging the job.workflow_dispatchlets you run it by hand to test.
Test it
Run the workflow once by hand. In Lateping, the check should show a start ping and then a success ping.
gh workflow run nightly-backup.yml gh run watch
Or use the Lateping action
The same pings in one step. Put it last in the job with if: always(), and pass the job’s status:
a successful job pings success, and a failed or cancelled one pings /fail.
name: Nightly backup
on:
schedule:
- cron: "17 2 * * *" # 02:17 UTC
workflow_dispatch:
jobs:
backup:
runs-on: ubuntu-latest
steps:
- uses: lateping/ping@v1
with:
url: ${{ secrets.LATEPING_URL }}
status: start
- uses: actions/checkout@v4
- name: Run backup
run: ./scripts/backup.sh
- uses: lateping/ping@v1
if: always()
with:
url: ${{ secrets.LATEPING_URL }}
status: ${{ job.status }}
A ping that can’t be sent logs a warning instead of failing your job. Set fail-on-error: 'true'
if you’d rather it failed. Source: lateping/ping.
Pinging from a Node script
If the job is a single script, you can ping from inside it instead. Pass the secret in as an environment variable with env: on the step.
const url = process.env.LATEPING_URL;
await fetch(`${url}/start`);
try {
await runBackup();
await fetch(url);
} catch (err) {
await fetch(`${url}/fail`, { method: "POST" });
throw err;
}
If GitHub disables the workflow
You’ll get a “late” email from us when the first run is missed. Turn the workflow back on as described in Check whether it happened to you. The next successful run sends a “recovered” email.