I've been building a GitHub Actions linter and didn't want to trust my own heuristics, so I ran it on real projects first: the most-starred JavaScript and TypeScript repos with 1,000–20,000 stars that were active since September 2026. That gave 149 repos, and 139 of them had workflows.

I only checked three things, all boring and all cheap to fix:

  1. Dependency cache: do CI workflows re-download every package on every run?
  2. Concurrency on pull requests: when you push again to a PR, does the outdated run get cancelled?
  3. Job timeouts: does each job have timeout-minutes, or does it fall back to GitHub's default of 6 hours?

The results

Check Result
Repos with PR workflows that have no concurrency group 97 of 129 (75%)
CI jobs with no timeout-minutes 1,329 of 2,181 (61%)
Repos that install deps with no cache in at least one CI workflow 41 of 121 (34%)
Repos that passed all three 1 of 139

These are popular, well-maintained projects run by experienced people. That isn't a criticism of them. It shows how easy these defaults are to miss.

Fix 1: cancel outdated PR runs (2 lines)

Without a concurrency group, every push to a PR starts a full new run while the previous one keeps going. Add this at the top level of the workflow:

concurrency: group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} cancel-in-progress: true 

For deploy workflows, use cancel-in-progress: false. You never want to cancel a deploy halfway.

Fix 2: put a timeout on every job (1 line)

A test waiting on a port that never opens, or a --watch flag left on in CI, can hold a runner for 6 hours.

jobs: test: runs-on: ubuntu-latest timeout-minutes: 15 # about 2x the job's normal duration 

Fix 3: cache dependencies (1 line)

- uses: actions/setup-node@v7 with: node-version: lts/* cache: npm # or yarn / pnpm 

Since setup-node v5, npm projects get this automatically if package.json has a packageManager field set to npm. My scanner accounts for that.

What I deliberately didn't count

I wanted the numbers to be conservative, not scary:

  • Local composite actions and reusable workflows are skipped for the cache check. I can't see inside them, and many of them do cache.
  • Release, publish and deploy workflows are skipped. They often avoid caches on purpose, as hardening against cache poisoning.
  • Explicitly disabled caches (package-manager-cache: false) are treated as a deliberate choice.
  • I checked 15 flagged repos by hand. After the first round I tightened a rule; in the second round, 6 of 7 were correct. The remaining one tests clean installs on purpose.

Bias warning: this is popular open source. Company repos may look better or worse.

Check your own repo

I turned the checks into a free web tool. Paste a public repo and get a 0–100 score with the exact file and line to change. No signup, and nothing is stored:

👉 devrescue.vercel.app

The method and the per-repo raw data are public: devrescue.vercel.app/research

If you think a heuristic is wrong, tell me in the comments. That's the most useful feedback I can get.