The first thing I check on a slow CI run is the cache, and the cache is usually missing. It is the cheapest speedup there is: one line on your setup step, and every run stops re-downloading and rebuilding the exact dependencies it already had yesterday. On a typical Node or Python project that is 30 to 90 seconds back, every run, forever. People skip it anyway, then pay GitHub to reinstall lodash for the ten-thousandth time.
The free win: built-in caching
You probably do not need actions/cache directly. The setup-* actions cache your package manager for you, you just have to turn it on:
# Node
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'npm' # or 'yarn' / 'pnpm'
# Python
- uses: actions/setup-python@v5
with:
python-version: '3.12'
cache: 'pip'
Go (actions/setup-go) caches by default. Java (actions/setup-java) needs cache: 'gradle' (or 'maven') set explicitly. Check your setup step first, the one-line fix covers most projects.
Custom caching with actions/cache
For anything the setup actions do not cover (a build output, a tool the package manager does not own), reach for actions/cache with an explicit path and key:
- uses: actions/cache@v4
with:
path: ~/.cache/my-tool
key: ${{ runner.os }}-mytool-${{ hashFiles('**/lockfile') }}
restore-keys: |
${{ runner.os }}-mytool-
Get the key right, or the cache never helps
The key should change only when your dependencies change, which is why it hashes the lockfile (hashFiles('**/package-lock.json')). The restore-keys prefix lets a run fall back to the most recent matching cache when the exact key misses, so a single dependency bump restores most of the cache instead of rebuilding everything.
Why your cache "isn't working"
- Caching the wrong directory: cache the package manager's store, not
node_moduleson most setups. - A key with no lockfile hash, so it never invalidates and you keep serving a stale cache.
- Cache scope: a branch reads its own cache plus the default branch's, but not a sibling branch's, so cross-branch hits can surprise you.
Find out if you're missing it
astro, for instance, has a release workflow with no dependency cache. Curious about yours? See real scorecards on the showcase, then scan your own.
I build GitSpider, which scans a repo's GitHub Actions and flags the missing cache (and the rest) with the fix for each. Free, no install for public repos: gitspider.com/scan.
Top comments (0)