GitHub Actions cache never restores because key includes github.run_id

2026-11-13 · 3 min read

GitHub Actions cache never restores because key includes github.run_id

A build that should reuse dependencies starts from scratch on every push. The cache step reports a miss, the install step downloads the same packages again, and CI time and billed minutes climb. The usual cause is a cache key that contains github.run_id. Because that value changes on every workflow run, the exact key changes too, so actions/cache never finds the previous entry unless a restore key happens to match.

Why github.run_id breaks cache matching

actions/cache restores a cache by matching the key exactly. If no exact match exists, it tries restore-keys as prefixes in order. github.run_id is unique per workflow run [link needed]. A key like this never repeats:


key: deps-${{ runner.os }}-${{ github.run_id }}-${{ hashFiles('package-lock.json') }}

The lockfile hash is stable, but the run ID in the middle means the full key is different every time. Without restore-keys, every run is a guaranteed miss. Even with restore keys, a prefix ending before the run ID saves you; a prefix that includes the run ID does not.

How to fix a GitHub Actions cache key run_id problem

Use a numbered procedure:

1. Find the cache step in the workflow and print the key it generates. Run the workflow with a debug step or inspect the key: expression directly.

2. Remove github.run_id from the exact key unless you need per-run isolation. Use a stable key based on the lockfile and OS:

```yaml

key: deps-${{ runner.os }}-${{ hashFiles('package-lock.json') }}

```

3. If you want the run ID for traceability, put it at the end and add restore-keys that stop before it:

```yaml

key: deps-${{ runner.os }}-${{ hashFiles('package-lock.json') }}-${{ github.run_id }}

restore-keys: |

deps-${{ runner.os }}-${{ hashFiles('package-lock.json') }}-

```

4. Keep the cache path narrow. Broad paths make a bad key more expensive because every miss writes a large archive.

5. Re-run the workflow and confirm the log says the cache was restored, not just saved.

Verify the restore instead of assuming it

Check the cache step log for a line beginning with Cache restored from key or a fallback message showing which restore key matched [link needed]. If you see Cache not found for input keys, the key or prefix is still wrong. Also compare save and restore events over two consecutive runs: the second run should restore what the first saved, unless the dependency hash changed.

A quick local check: copy the key expression into a scratch workflow, run it twice without changing dependencies, and compare the resolved keys in the logs. If they differ and no restore key covers the stable prefix, you have reproduced the bug.

When NOT to do this

Do not remove github.run_id from the key if the cache must be immutable per run. Examples include build outputs that are consumed by later jobs only within the same run, or caches you deliberately isolate to avoid stale artifacts after a bad dependency state. In those cases, accept the miss and write a fresh cache. Also leave the run ID out of restore-keys only when you are comfortable restoring an older entry; if you need strict identity, use the exact key and skip restore keys entirely.

Checklist