---
title: "GitHub Actions monitoring for scheduled workflows | Logdash"
description: "Scheduled workflows get disabled after 60 days and run late under load. How to catch the run that never happened and get a Telegram alert."
url: https://logdash.io/cron-monitoring/github-actions
---

# GitHub Actions monitoring

GitHub shows you the runs that failed but never the runs that did not start, so watch a scheduled workflow from outside: the last step reports success, and a monitor alerts you when that report goes stale.

The Actions tab is a good record of what ran and no record of what did not. When a scheduled workflow fails, GitHub emails the user who last modified the cron line in the workflow file, which may be someone who left in March. When the workflow never starts, nobody hears anything, because there is no run to fail.

## GitHub Actions scheduled workflow monitoring

Three behaviours from GitHub's own docs make a schedule less reliable than the YAML suggests:

- In a public repository, scheduled workflows are disabled after 60 days with no repository activity. The nightly backup of a finished side project is exactly the workflow this hits.
- The schedule event can be delayed during high load, and the start of every hour counts as high load. Under enough load, queued runs are dropped, not just delayed.
- Schedules run only on the default branch, no more often than every 5 minutes, and in UTC unless the entry sets the timezone key GitHub added in March 2026.

So the question to monitor is not "did a run fail". It is "did a successful run happen recently enough".

## GitHub Actions cron: report in on success

Put the report at the end of the job. Steps stop at the first failure, so the last step only runs when everything before it passed. The odd minute keeps the run off the top of the hour.

 .github/workflows/nightly-sync.yml 

```yaml
name: nightly-sync
on:
  schedule:
    - cron: '17 3 * * *' # 03:17 UTC
  workflow_dispatch:

jobs:
  sync:
    runs-on: ubuntu-latest
    timeout-minutes: 30
    steps:
      - uses: actions/checkout@v7
      - run: ./scripts/sync.sh
      - name: Report success
        run: >
          curl -fsS -m 10 -X POST
          -H "Authorization: Bearer ${{ secrets.HEARTBEAT_TOKEN }}"
          https://yourapp.com/internal/heartbeat/nightly-sync
```

## Why the check lives in your app

A Logdash push monitor looks like the obvious target and is the wrong one here. Push monitors are a Pro feature, they expect a ping inside every 15-second window, and they flip to down on the first empty one. There is no grace setting. A workflow that runs every 5 minutes at best would flap after every run. So turn it around: the workflow tells your app, your app keeps the mark for 26 hours, and an ordinary HTTP monitor asks whether the mark is still there. That runs on the free plan. The 26 hours is a daily schedule plus two hours of slack for GitHub delays; set it to your own interval plus the lateness you can live with.

 server.ts 

```typescript
import express from 'express';
import { Redis } from 'ioredis';

const app = express();
const redis = new Redis(process.env.REDIS_URL!);
const KEY = 'heartbeat:nightly-sync';
const TTL = 26 * 60 * 60; // one day plus slack for GitHub delays

app.post('/internal/heartbeat/nightly-sync', async (req, res) => {
  if (req.get('authorization') !== `Bearer ${process.env.HEARTBEAT_TOKEN}`) {
    res.sendStatus(401);
    return;
  }
  await redis.set(KEY, Date.now(), 'EX', TTL);
  res.sendStatus(204);
});

// Logdash checks this. 503 means no successful run in 26 hours.
app.get('/health/nightly-sync', async (_req, res) => {
  res.sendStatus((await redis.exists(KEY)) ? 200 : 503);
});

app.listen(3000);
```

1. **Ship the two routes** Deploy them, add HEARTBEAT\_TOKEN as a repository secret, then run the workflow once by hand so the key exists before the first check.
2. **Point a monitor at it** Create a service in Logdash with an HTTP monitor on /health/nightly-sync and connect a Telegram channel. Checks run every 5 minutes on the free plan and every minute on Builder.
3. **Let it go stale** Delete the key in Redis. The route answers 503, the monitor flips to down on the next check, and a Telegram alert names the monitor and the 503.

### Logdash vs Healthchecks.io for scheduled workflows

| Feature                                   | Logdash                               | Healthchecks.io                                     |
| ----------------------------------------- | ------------------------------------- | --------------------------------------------------- |
| Code in your app                          | Two routes and a Redis key            | None, the workflow curls their URL                  |
| Schedule awareness                        | Expressed as the key TTL              | Cron expression, time zone and grace time per check |
| Catches the 60-day disable                | Yes, the key expires                  | Yes, the ping stops                                 |
| HTTP checks on the app the workflow feeds | Same service, with response time      | Not offered, it only listens for pings              |
| Free plan                                 | Five services, checks every 5 minutes | 20 checks                                           |
| Alert channels                            | Telegram and webhook                  | Email, Slack, Telegram and many more                |

### When Healthchecks.io is the better pick

- You do not want to add routes to an app just to watch a workflow. A curl to their ping URL is the whole integration.
- You run many scheduled workflows. One check per workflow with its own cron expression beats one route per workflow.
- The workflow does not touch an app you run, for example it cuts a release or prunes a bucket.
- You want email or Slack alerts. Logdash sends Telegram and webhooks only.

### Is there a GitHub Actions monitoring dashboard? 

Yes, under Insights: Actions Usage Metrics and Actions Performance Metrics, the latter on every GitHub Cloud plan since March 2025\. They show run times, queue times and failure rates per workflow. They count runs that happened, so a schedule that silently stopped never shows up there.

### How does GitHub Actions runner monitoring work? 

GitHub-hosted runners are GitHub's problem. A self-hosted runner installed with ./svc.sh install is a long-running service, which suits a Logdash push monitor: a loop on the host that pings every 10 seconds while the actions.runner service is active. GitHub marks a dead runner Offline in settings, which is a page you have to open.

### Why did my GitHub Actions cron not run? 

Usually one of four reasons: the repo is public and had no activity for 60 days, so the schedule was disabled; the run was delayed or dropped under load; the workflow is not on the default branch; or the entry has no timezone key, so it runs in UTC, and you wrote local time. Re-enable a disabled workflow from the Actions tab.

### Does GitHub Actions scheduled workflow monitoring need a paid plan? 

Not with the setup above. It uses an HTTP monitor, which the free plan includes with checks every 5 minutes. Push monitors are Pro only at $15 a month, and they need a ping every 15 seconds, which a scheduled workflow cannot send.

## Keep reading

[All cron monitoring guides](https://logdash.io/cron-monitoring): One guide per scheduler, each with the code that proves a job ran and the alert for when it did not.

[Windows Task Scheduler monitoring](https://logdash.io/cron-monitoring/windows-task-scheduler): Task Scheduler keeps each task's last run time and last run result but never alerts on them, so monitor it with a small PowerShell watcher that pings an external monitor every 10 seconds while the last run is recent and clean.

[Scheduled task monitoring](https://logdash.io/cron-monitoring/scheduled-tasks): Scheduled task monitoring means the task leaves proof after every clean run, whatever scheduler starts it, and a service outside the box alerts you when that proof gets older than the schedule allows.

[Dead man's switch monitoring](https://logdash.io/cron-monitoring/dead-mans-switch): A dead man's switch alerts on the absence of a signal: the job checks in after every successful run, and when the check-ins stop, for any reason at all, the switch fires.

[Uptime monitoring](https://logdash.io/features/monitoring): HTTP checks as often as every 15 seconds. When one fails, Telegram tells you before your users do.
