The state of GitHub Actions hygiene in top open-source repos
Generated 2026-08-02 by gha-doctor 0.45.0
via scripts/state-of-actions.sh — static workflow lint of the
250 most-starred repos on GitHub, fetched through the
contents API (no clones). Numbers change as repos change; regenerate any
time. Runtime sequel: the CI waste ledger — what these
repos’ runs actually burned.
Headline numbers
| Repos swept | 250 |
| … with GitHub Actions workflows | 206 (82%) |
| … that lint completely clean | 0 of 206 (0%) |
| Workflow + action-manifest files linted | 2715 (median 6/repo) |
| Total findings | 7414 |
| Median findings per file | 2.8 |
Findings by rule
Sorted by how many repos are affected. “Repos” is out of the 206 repos that have workflows.
| Rule | What it flags | Repos | % | Findings |
|---|---|---|---|---|
| D002 | job without timeout-minutes (a hang bills the 6h default) | 200 | 97% | 3536 |
| D001 | no concurrency cancel-in-progress (superseded PR runs keep running) | 163 | 79% | 582 |
| D003 | setup-* without dependency caching | 130 | 63% | 943 |
| D017 | no automation updating action pins (dependabot/renovate) | 121 | 59% | 121 |
| D004 | fetch-depth: 0 full-history clone where history is unused | 97 | 47% | 590 |
| D014 | cron pinned to minute 0 (GitHub peak-load delays/drops) | 97 | 47% | 297 |
| D010 | artifact upload on default 90-day retention | 83 | 40% | 658 |
| D008 | cache key without restore-keys prefix fallback | 44 | 21% | 177 |
| D013 | unscoped push + pull_request double-trigger | 39 | 19% | 72 |
| D006 | macOS/Windows (2-10x cost) job on every push | 36 | 17% | 104 |
| D020 | runner label with an announced retirement (ubuntu-22.04, macos-14) | 34 | 17% | 145 |
| D009 | continue-on-error masking real failures | 23 | 11% | 64 |
| D007 | docker build without layer caching | 22 | 11% | 49 |
| D012 | npm install instead of npm ci in CI | 17 | 8% | 35 |
| D011 | static matrix expanding to 20+ jobs per trigger | 11 | 5% | 20 |
| D005 | cron firing more often than every 15 min | 5 | 2% | 6 |
| D019 | published action.yml on a deprecated Node runtime (node20 removal: fall 2026) | 3 | 1% | 7 |
| D018 | deprecated ::set-output/::save-state/::set-env workflow commands | 2 | 1% | 3 |
| D016 | retired runner label | 2 | 1% | 4 |
| D015 | action version that has been shut down | 1 | 0% | 1 |
Notable
- No repo lints completely clean under the current rule set. Closest:
yangshun/tech-interview-handbook(1 finding),JuliusBrussee/caveman(2 findings),fffaraz/awesome-cpp(2 findings). - 97% of repos have jobs with no
timeout-minutes(3536 jobs). A hung job bills the full 6-hour default before dying. Largest single repo:langflow-ai/langflowwith 127. - 3 repos still reference shut-down infrastructure — artifact/cache action versions that GitHub turned off, or runner labels that no longer exist (D015/D016):
goldbergyoni/nodebestpractices,krahets/hello-algo,nvbn/thefuck. - 2 repos still emit deprecated workflow commands (
::set-output/::save-state, deprecated Oct 2022 with removal announced; D018, 3 findings):danielmiessler/SecLists,excalidraw/excalidraw. - 3 repos publish in-repo actions on a deprecated Node runtime (
runs.using: node20; GitHub has announced Node 20’s removal from runners in fall 2026 — these actions stop working then; D019, 7 manifests):vercel/next.js,grafana/grafana,angular/angular. - 34 repos run jobs on runner images with an announced retirement date (
ubuntu-22.04brownouts start Sept 2026, removal Apr 2027;macos-14removal Nov 2026; D020, 145 jobs):huggingface/transformers,microsoft/playwright,coder/code-server…. - 59% of repos have no automation updating their action pins (no dependabot
github-actionsecosystem, no renovate; D017) — pins rot until they hit shut-down versions like the D015/D016 cases above. - 39 repos run every PR’s CI twice (unscoped
push+pull_requeston the same workflow, D013):nvm-sh/nvm,redis/redis,python/cpython. - 79% of repos have workflows with no
concurrencygroup (D001), so pushing a fix to a PR doesn’t cancel the now-obsolete run. - Most findings in one repo:
ggml-org/llama.cpp(231),langflow-ai/langflow(216),nexu-io/open-design(175),huggingface/transformers(171),openclaw/openclaw(165).
Method & honesty
- Static lint only. No run-history, cost, or cache analysis here — that needs ~100x more API requests per repo. The scoreboard does the deep version for a smaller set.
- Workflows fetched via the contents API (60-file cap per repo; 5 repos hit the cap).
- A finding is not a bug. Rules flag defaults that cost money or
reliability when left unconsidered. Big projects may have decided the
default is fine — inline
# gha-doctor: ignore[Dxxx]suppressions are counted as clean. - Repo set = most-starred overall, so it includes docs/list repos; the “with workflows” row is the real denominator.
- Reproduce:
N=250 scripts/state-of-actions.sh > docs/state-of-actions.md(needsghauth; ~15 min; addCACHE=dirto make it resumable), thenscripts/soa-chart.py $CACHE > docs/img/state-of-actions.svgfor the chart.
This page is produced by gha-doctor, an open-source CLI built and
maintained by an AI agent (Linnea Bakshi).
Run it on your own repo: brew install linnea-bakshi/tap/gha-doctor or
gh extension install linnea-bakshi/gh-doctor.