
结论先行
用 actions/cache 时,如果缓存体积超过 10GB,save 操作会静默失败——CI 跑 ✅ success,但缓存根本没存进去。本文讲清楚 actions/cache v4 的三个行为差异,以及如何正确配置缓存。
背景
GitHub Actions 的 actions/cache action 用来缓存依赖加速 CI 构建。Node.js 项目缓存 node_modules,Python 项目缓存 venv,原理不复杂:第一次跑下载依赖,后面直接从缓存恢复。
但深入用之后,有几个坑是文档里不会写的。
第一个坑:save 和 restore 是两个独立操作
actions/cache 实际上是两个操作:
- restore:用 key 查找缓存,找到了就恢复,找不到就用 restore-keys 做前缀匹配
- save:把当前 path 目录打包存进缓存
关键点:restore 和 save 之间没有任何关联。save 操作不会自动使用 restore-keys 的匹配结果,它只管把当前目录打一个新缓存,key 完全由你写的 key 字段决定。
- uses: actions/cache@v4
with:
path: node_modules
key: node-modules-${{ hashFiles('package-lock.json') }}
restore-keys: |
node-modules-
这个配置:
- restore 阶段:先用精确 key node-modules-xxxxx 找,找不到再用前缀 node-modules- 找任意匹配项恢复
- save 阶段:用精确 key node-modules-xxxxx 存一个新缓存
看起来没问题。但下一个坑就来了。
第二个坑:缓存超过 10GB,save 静默失败
这是我在一个 node_modules 体积接近 10GB 的项目中遇到的。
GitHub Actions 缓存有 10GB 的大小限制(免费版)。当缓存体积超标时,save 操作会失败,但 actions/cache@v4 的 save 失败是 warning,不是 error。
CI 日志里你看到的是:
Cache Size: 12.3 GB (exceeds 10 GB limit)
Cache save failed
Runner is out of disk space.
然后 CI 还是显示 ✅ success——因为 save 失败不会导致 step 报错退出。
这就意味着:你以为 CI 正常跑完了,缓存下次应该会 hit,结果下次 CI 还是老老实实下载依赖,缓存根本没存进去。
更坑的是,这个问题在 GitHub Actions 社区有大量反馈,但官方一直没有让 save 失败时自动报错。
临时解决方案:在 save 之后用一个 step 估算缓存体积,如果明显超过 10GB 就主动报警:
- name: Check cache size
run: |
SIZE=$(du -sh node_modules | cut -f1)
echo "node_modules size: $SIZE"
if [[ "$SIZE" == *"G"* ]]; then
echo "::warning::Cache directory is large, save may fail silently"
fi
第三个坑:restore-keys 的前缀匹配行为
restore-keys 做的是前缀匹配,而不是模糊匹配。
restore-keys: |
node-modules-
node-
如果精确 key node-modules-abc123 没命中,Actions 会按顺序尝试:
1. node-modules- + 任意后缀 → 匹配所有以 node-modules- 开头的缓存
2. node- + 任意后缀 → 匹配所有以 node- 开头的缓存
匹配到多个时,返回的是最近创建的那个。
这里有个实际影响:如果多个分支同时跑 CI,main 分支刚存的缓存是其他分支没见过的。此时 feature 分支用 restore-keys 匹配,会得到 main 分支的缓存——但这个缓存是 compatible 的,因为 package-lock.json 的 hash 一样。
如果 hash 不一样(package-lock.json 有变化),精确 key 不会命中,restore-keys 只能恢复到前一个版本的状态,然后 save 会存一个新的。
正确的缓存配置
结合踩坑经验,最佳实践:
- uses: actions/cache@v4
with:
path: node_modules
key: node-modules-${{ hashFiles('package-lock.json') }}
restore-keys: |
node-modules-
node-
三个要点:
- 用文件 hash 做 key:不要用日期或 commit sha 作为 key 的一部分,那样每次 commit 都会 miss 缓存
- 多层 restore-keys:提供逐级宽泛的前缀匹配,减少 hash 变化时的 miss 惩罚
- 不要依赖 save 会报错:save 失败不阻塞 CI,需要自己在后续 step 做检查
超过 10GB 怎么办
如果依赖体积经常超标,有几个思路:
方案一:拆缓存
把 devDependencies 和 dependencies 分开缓存,因为它们的 hash 通常不同步变化:
- uses: actions/cache@v4
with:
path: node_modules/prod
key: node-prod-${{ hashFiles('package-lock.json') }}
restore-keys: |
node-prod-
- uses: actions/cache@v4
with:
path: node_modules/dev
key: node-dev-${{ hashFiles('package-lock.json') }}
restore-keys: |
node-dev-
方案二:换源
把 npm/pip 源换成国内镜像,下载速度快到不需要缓存。
方案三:自己处理缓存逻辑
用 actions/cache@v4 的 save-always 参数强制每次都 save,或者完全自己用 upload-artifact / download-artifact 实现缓存逻辑。
总结
actions/cache 三个核心要点:
- save 不依赖 restore:save 的 key 就是你写的 key,和 restore-keys 无关
- save 失败不报错:超过 10GB 时静默失败,需要自己检查
- restore-keys 是前缀匹配:按顺序尝试,找最近创建的那个
下次 CI 缓存没 hit,先翻日志找 Cache save failed 和 exceeds 10 GB limit。
更多交流点击入群






