Reference
Verdicts
| Verdict | When |
|---|---|
| ON TIME | runs exist, median lag ≤ --late, under 25% very late, nothing missed |
| LATE | median lag above --late, or ≥ 25% of runs very late |
| DROPPING | at least one slot missed |
| NOT FIRING | slots came due and no scheduled run was created at all |
| DISABLED | workflow state is not active (e.g. disabled_inactivity) |
| INVALID | no valid cron, or the file does not parse |
| NO DATA | no slot has come due in the window |
Slot states
ontime (≤ on-time), late (≤ late), verylate, overdue (no run yet), missed, pending (not due yet).
Cause checks
| Code | Meaning |
|---|---|
disabled-inactivity | GitHub disabled the workflow after 60 days without repo activity; fix prints gh workflow enable |
disabled | workflow disabled by hand |
inactivity-soon | public repo with no push for 45+ days |
not-registered | no scheduled run since the file last changed, 2+ slots ago |
top-of-hour | cron fires at minute 0 |
too-frequent | interval under 5 minutes |
bad-cron, yaml | invalid cron line / unparsable file |
fork, archived | schedules off in forks until enabled; never in archived repos |
no-dispatch | no workflow_dispatch, so missed slots can't be run |
scheduler-side | lag happens before creation (GitHub), with the median start delay of manual runs as evidence |
queued | runs wait 5+ minutes after creation: runner capacity |
timezone | cron evaluated in an IANA timezone |
JSON
{ "repo", "repoUrl", "from", "now", "days", "verdict",
"workflows": [ { "file", "name", "path", "url", "state", "crons": [{ "cron", "timezone" }],
"changes": [{ "at", "crons" }], "hasDispatch", "verdict", "headline",
"stats": { "due", "ran", "missed", "overdue", "pending", "onTime", "late", "veryLate", "within1h",
"lagMin", "lagMedian", "lagP90", "lagMax", "queueMedian", "dispatchQueueMedian", "extraRuns" },
"slots": [ { "at", "cron", "status", "lagMs", "queueMs", "run": { "id", "number", "url", "created", "conclusion" } } ],
"findings": [ { "level", "code", "message", "fix" } ] } ],
"unscheduled": [ "ci.yml", … ] }
Times are epoch milliseconds in slots, ISO strings at the top level. Durations are milliseconds.
Exit codes
0 ok (or nothing matched --fail-on) · 1 a workflow matched --fail-on · 2 usage error (including an unknown option) / invalid cron · 3 GitHub API error (rate limit, not found, auth).
MCP tools
schedule_report returns the Markdown report plus compact JSON; catchup_plan returns the steps catchup would take (never dispatches); cron_explain returns next fire times, shortest interval and the minute-0 flag. All are annotated readOnlyHint: true.