Cache a Directory in GitHub Actions CI with restore-keys
Cache a directory with actions/cache. The restore-keys input is a block of text, one prefix per line, and a YAML list parses as a list instead of text.
A workflow reinstalls the same dependencies on every run, and the cache step needs a key that changes only with the lock file.
- uses: actions/cache@v4
with:
path: ~/.npm
key: npm-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
npm-import yaml
step = yaml.safe_load("""
- uses: actions/cache@v4
with:
path: ~/.npm
key: npm-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
npm-
""")[0]
w = step["with"]
print(repr(w["key"]))
print(repr(w["restore-keys"]))
as_list = yaml.safe_load("""
with:
restore-keys:
- npm-
- node-
""")
print(repr(as_list["with"]["restore-keys"]))"npm-${{ hashFiles('**/package-lock.json') }}"
'npm-\n'
['npm-', 'node-']The step names a path, a key built with hashFiles on the lock file, and restore-keys as fallbacks. A pipe after restore-keys: makes a YAML block scalar, a single string with one prefix per line, which is the form the action reads: PyYAML shows a string that ends in a newline. Written as a YAML list, the same field becomes ['npm-', 'node-'], a list where the action expects text. The key itself is an ordinary string with the expression inside. See the actions/cache repository.
A cache entry is never updated once saved under a key, so a key that does not change with the dependencies keeps stale files; the hash in the key is what forces a new one. Prefix matches in restore-keys restore the newest entry that starts with the prefix. The parser here is PyYAML and not GitHub's, but the YAML types are the same.