GitHub Actions 把 “免费缓存” 作为卖点,一行 uses: actions/cache@v4 就能让 node_modules 瞬间复活,看起来是 CI 性能优化的终点。但真正跑过上百条工作流之后,你会发现:缓存命中率一旦低于 80%,平均构建时间不但没降,反而比 “裸跑” 更长。问题的根子不在网络,而在 GitHub 把缓存钥匙完全交给用户 ——key 设计稍有缺陷,就会触发 “越缓存越慢” 的反向效应。本文把常见暗坑拆成三类,并给出可直接抄的四组参数模板,帮你把命中率稳在 90% 以上。
一、跨 OS 路径漂移:同样 key 不同哈希
官方示例几乎清一色写成:
key: ${{ runner.os }}-node-${{ hashFiles('package-lock.json') }}
这在 Ubuntu 上跑得欢,一换到 Windows (cmd) 或 macOS,同样的 package-lock.json 却可能得到不同哈希。原因出在 Git 的 core.autocrlf 与文件系统时间戳差异;更隐蔽的是,Windows 跑 npm ci 会在 %AppData%\npm-cache 里生成 CRLF 结尾的元数据,Linux 却在 ~/.npm 下保持 LF。结果缓存解压后,路径对不上,post 步骤重新压缩时又把 CRLF 文件打包进去,下一次 Windows 命中后再次解压 —— 循环污染,命中率逐次下降。
修正:把 “内容哈希” 与 “运行环境” 彻底解耦。
- name: Normalize line endings
run: git config --global core.autocrlf false
- name: Cache npm
uses: actions/cache@v4
with:
path: ~/.npm
key: node-${{ hashFiles('package-lock.json') }}-${{ runner.arch }}
restore-keys: |
node-${{ hashFiles('package-lock.json') }}-
把 ${{ runner.os }} 从 key 主干降级到 “可选维度”,避免跨 OS 共享同一条缓存,却又不让路径差异污染哈希。
二、restore-keys 污染:前缀回退把旧依赖带回来
很多人为了 “提高命中率”,把 restore-keys 写成:
restore-keys: |
${{ runner.os }}-node-
这意味着只要操作系统对上,任何老版本 node_modules 都能被拉回来。若最近一条缓存是三天前的,里面可能带着被 CVE 标记的旧包;更惨的是,你刚刚在 package.json 里把 `