GitHub Actions 缓存 save 失败不报错:我踩过的三个坑

封面图

结论先行

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-

三个要点:

  1. 用文件 hash 做 key:不要用日期或 commit sha 作为 key 的一部分,那样每次 commit 都会 miss 缓存
  2. 多层 restore-keys:提供逐级宽泛的前缀匹配,减少 hash 变化时的 miss 惩罚
  3. 不要依赖 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@v4save-always 参数强制每次都 save,或者完全自己用 upload-artifact / download-artifact 实现缓存逻辑。

总结

actions/cache 三个核心要点:
- save 不依赖 restore:save 的 key 就是你写的 key,和 restore-keys 无关
- save 失败不报错:超过 10GB 时静默失败,需要自己检查
- restore-keys 是前缀匹配:按顺序尝试,找最近创建的那个

下次 CI 缓存没 hit,先翻日志找 Cache save failedexceeds 10 GB limit

© 版权声明
THE END
喜欢就支持一下吧
点赞5 分享