didrun
by ubukhari
README.md
# didrun
Did your scheduled GitHub workflows run when they should have? didrun reads GitHub's own record of a
repo's workflows and scheduled runs and lists the runs that came late, the runs that never came, the
runs that failed, the green runs that did nothing, and the schedules GitHub has quietly disabled.
```sh
npx --allow-git=root github:agentwares/didrun
```
Run it inside a clone, or name the repo (`… didrun octo-org/nightly-jobs`). No account, no change
to your repo, no code to add. It uses your own GitHub token (`GITHUB_TOKEN`, `GH_TOKEN` or the
GitHub CLI's login), and with none it reads public repos.
GitHub's own documentation (read 9 October 2026) says scheduled runs "can be delayed during periods
of high loads", that under enough load "some queued jobs may be dropped", and that in a public
repository "scheduled workflows are automatically disabled when no repository activity has occurred
in 60 days". None of that sends an alert. A dropped run leaves no trace in the Actions tab, and a
disabled schedule simply stops.
## What it prints
Recorded over a **synthetic** repo shipped with the package (`npx --allow-git=root github:agentwares/didrun demo`,
regenerated by `node scripts/sample.mjs`), not anyone's real runs:
```text
Synthetic repo, written for this demo: not anyone's repo or runs.
didrun 0.1.0 · synthetic-org/nightly-jobs · Fri 2 Oct 13:00 UTC to Fri 9 Oct 13:00 UTC (7 days) · recorded synthetic fixtures
4 scheduled workflows: 36 runs due, 34 ran (32 on time, 2 late), 2 missed; 1 failed, 1 green but did
nothing and 1 workflow disabled.
Nightly backup · .github/workflows/backup.yml
schedule 0 3 * * * (UTC)
runs due 7: 6 ran, 1 late, 1 missed; 1 failed, 1 did nothing
missed Sun 4 Oct 03:00 UTC no run was created
did nothing Mon 5 Oct 03:00 UTC green in 6 s; its usual success takes 4 min 8 s
late Tue 6 Oct 03:00 UTC the run was created 2 h 17 min late
failed Thu 8 Oct 03:00 UTC the run failed after 1 min 30 s
last run Fri 9 Oct 03:04 UTC, success
next due Sat 10 Oct 03:00 UTC
fix GitHub delays and drops scheduled runs under high load, which includes the start of
every hour: move the minute off :00 (e.g. 17 instead of 0)
open the green runs that were short: usually every job or step was skipped by an
`if:`, or the job found nothing to do and exited 0
Sync prices · .github/workflows/sync.yml
schedule 0 */6 * * * (UTC)
runs due 28: 27 ran, 1 late, 1 missed
missed Sun 4 Oct 06:00 UTC no run was created
late Wed 7 Oct 18:00 UTC the run was created 47 min late
last run Fri 9 Oct 12:03 UTC, success
next due Fri 9 Oct 18:00 UTC
fix GitHub delays and drops scheduled runs under high load, which includes the start of
every hour: move the minute off :00 (e.g. 17 instead of 0)
Close stale issues · .github/workflows/stale.yml
schedule 0 0 * * * (UTC)
state disabled by GitHub: no repository activity in 60 days (public repositories only)
last run Sun 9 Aug 00:07 UTC, success (61 days ago)
fix gh workflow enable stale.yml -R synthetic-org/nightly-jobs
then keep the repo active: GitHub disables a public repo's scheduled workflows again
after 60 days without activity
Weekly report · .github/workflows/report.yml
schedule 30 8 * * 1 (America/New_York)
runs due 1: on time
last run Mon 5 Oct 12:41 UTC, success (4 days ago)
next due Mon 12 Oct 12:30 UTC
Not scheduled: 1 workflow (ci.yml).
This repository is public and its last push was 48 days ago. GitHub disables a public repo's
scheduled workflows after 60 days without activity: around 20 Oct 2026 (in 12 days), unless there is
activity first.
An hourly watch for scheduled runs is not built.
Would you pay for one that emails you when a run is late, missed or did nothing? One click counts:
https://agentwares-agentcheck.vercel.app/didrun?ref=didrun#watch
```
A real one: on 9 October 2026, read without a token, both of `actions/stale`'s own Sunday 03:00 UTC
schedules had run on 4 October, but 1 h 4 min and 1 h 18 min late.
## Usage
```sh
npx --allow-git=root github:agentwares/didrun # the current checkout's origin, last 7 days
npx --allow-git=root github:agentwares/didrun owner/repo --days 30 # another repo, up to 30 days back
npx --allow-git=root github:agentwares/didrun --workflow nightly.yml # one workflow, by file name, path or name
npx --allow-git=root github:agentwares/didrun --late 60 # count a run as late after 60 minutes (default 30)
npx --allow-git=root github:agentwares/didrun --json # the report as JSON
npx --allow-git=root github:agentwares/didrun --fail-on missed # exit 1 if a run never came or a schedule is disabled
npx --allow-git=root github:agentwares/didrun demo # the synthetic repo above; no network
```
**How a run is matched to its time.** For every workflow with an `on.schedule` on the default
branch, didrun lists the times its cron lines fire over the window, in UTC or the line's `timezone`
(with GitHub's rule for the hour clocks skip). It counts only from when the file last changed, so a
new schedule is not blamed for runs before it existed. GitHub's run list does not say which
scheduled time a run answered, so a run belongs to the latest time due at or before it was
created. When two runs land between the same two times, the earlier one counts as a late run of the
time before. A time with no run is **missed** once it is more than `--late` minutes old, and
**pending** until then. For schedules more frequent than GitHub's delay, a late run and a missed one
cannot be told apart from the record. The count of runs that never came is right either way.
**Green but did nothing** means a run whose conclusion is `skipped`, or a success shorter than a
fifth of the workflow's usual (median) successful run, with at least three successes to compare.
**What it reads, and where it sends it.** Only GET requests to GitHub's API (`api.github.com`, or
`GITHUB_API_URL` on a GitHub Enterprise runner):
- the repo: visibility, default branch, last push;
- its workflows and their `state`;
- each workflow file on the default branch;
- the file's last commit;
- its `event=schedule` runs in the window.
The token goes in the Authorization header of those requests and nowhere else. It is never printed,
logged or stored. A private repo needs Actions and Contents read access (the `repo` scope on a
classic token). Without a token GitHub allows 60 requests an hour, enough for a few checks of a
small repo.
**Exit codes:**
- 0: the check ran (whatever it found, unless `--fail-on` says otherwise);
- 1: an error, or `--fail-on` matched;
- 2: a usage mistake.
With `--json`, an error is `{"error": {"code", "cause", "fix", "retryable"}}`.
**What `--json` gives:**
- `summary`, `verdict` (`ok`, `problems` or `nothing-scheduled`), and `inactivity` (public repos:
`days_since_push`, `disables_on`, `days_left`);
- for each workflow: `state`, `disabled`, `schedules`, `evaluated_from`, `counts`,
`usual_success_seconds`, `last_run` and `next_due`;
- `findings`, each with `kind` (`missed`, `late`, `pending`, `failed`, `cancelled`, `did_nothing`),
`due` and the `run`;
- `fix`, then `unscheduled`, `not_on_default_branch` and `handoff`.
## Why scheduled runs go missing
From GitHub's [events that trigger workflows](https://docs.github.com/en/actions/reference/workflows-and-actions/events-that-trigger-workflows)
and [workflow syntax](https://docs.github.com/en/actions/reference/workflows-and-actions/workflow-syntax)
pages, read 9 October 2026:
- **Load.** Scheduled runs are delayed when GitHub Actions is busy, and "high load times include the
start of every hour". Under enough load, queued jobs are dropped. didrun suggests moving a
minute-0 line off the hour when it finds late or missed runs.
- **The default branch.** A schedule runs only from the workflow file on the default branch.
- **60 days.** In a public repository, scheduled workflows are disabled after 60 days with no
repository activity, and stay disabled until someone enables them (`gh workflow enable`). Forks
start with them disabled.
- **UTC.** A line runs in UTC unless it sets `timezone`, an IANA name. The shortest interval is
every 5 minutes.
One page per cause, with how to check by hand: [agentwares-agentcheck.vercel.app/didrun](https://agentwares-agentcheck.vercel.app/didrun).
## The GitHub Action
```yaml
# .github/workflows/didrun.yml
on:
schedule:
- cron: "23 8 * * *"
push:
branches: [main]
workflow_dispatch:
permissions:
actions: read
contents: read
jobs:
didrun:
runs-on: ubuntu-latest
steps:
- uses: agentwares/didrun@main
with:
fail-on: missed # or problem, or never (default)
```
It runs the committed `dist/` on your runner, with no install, writes the report to the job summary,
and sets `missed`, `late`, `problems` and `report` (the JSON's path) as outputs. Inputs: `repo`
(default: this one), `days`, `late-minutes`, `fail-on` and `token`.
A check on GitHub's scheduler shares its delays and drops, and in a public repo its own schedule
falls under the same 60-day rule. So run it on pushes and by hand too, as above.
[`examples/github-workflow.yml`](examples/github-workflow.yml) has the whole file.
## For agents: an MCP tool
`didrun mcp` serves one tool over stdio, `didrun_check_schedules` (`repo`, `days`, `late_minutes`,
`workflow`; all optional). It returns the same report as `--json`. It is read-only and calls
GitHub's API, and its annotations say so. Add it to Claude Code with:
```sh
claude mcp add didrun -- npx -y --allow-git=root github:agentwares/didrun mcp
```
## Inside Claude Code
The repo is also a Claude Code plugin marketplace with one skill, `/didrun:didrun`. It runs the
check and walks through each finding with what GitHub documents as the cause, and proposes fixes
for you to approve:
```text
/plugin marketplace add agentwares/didrun
/plugin install didrun@didrun
```
## An hourly watch for scheduled runs is not built
didrun tells you what happened when you ask. A watch would ask for you, every hour, from outside
GitHub's scheduler, and email you when a run is late, missed, or green with nothing done, with no
change to your repo. It does not exist yet: agentcheck's free hourly watch covers MCP servers
only. If you would pay for this one, say so with one click on
[agentwares-agentcheck.vercel.app/didrun](https://agentwares-agentcheck.vercel.app/didrun?ref=didrun#watch).
The click is counted, and nothing else is sent.
## License
MIT, by [agentwares](https://github.com/agentwares). didrun is a free tool from
[agentcheck](https://agentwares-agentcheck.vercel.app/?ref=didrun), which runs scheduled checks of
your endpoints and MCP servers and alerts you when an answer changes.
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo issues