A cache step with a hit rate of zero for four months, because the key included something that changed on every run.
# the broken key
key: composer-${{ github.sha }}-${{ hashFiles('composer.lock') }}
# github.sha is unique per commit. the key never repeats.
# it was written to force a refresh once, in 2023-07.
# the working version
key: composer-${{ runner.os }}-${{ hashFiles('**/composer.lock') }}
restore-keys: |
composer-${{ runner.os }}-
A cache that never hits fails silently and costs a little time on every run to write an entry nobody reads. The metric that surfaces it is in the step log — “Cache not found for input keys” on a job that has run four hundred times — and nothing summarises that, so it needs looking for. restore-keys is what makes a partial match useful when the lock file has changed by one package.