GitHub Actions 缓存键怎样按依赖锁文件精确失效缓存为什么结果为空或不符合预期
来源:17golang原创
时间:2026-11-10 01:37:25 494浏览 收藏
GitHub Actions 里的依赖缓存,最容易出现的错觉是“我改了 package-lock.json,下一次运行却还在用旧内容”。真正决定缓存是否失效的不是缓存目录本身,而是 key 的最终值:把锁文件交给 hashFiles() 计算后,依赖文件变化就会得到一个新键;如果只写固定字符串,缓存当然不会随着依赖变化。
官方地址:https://github.com/
下面以 npm 的 package-lock.json 为例,把配置、可见结果和排查路径串起来。pnpm、Yarn、Go 依赖只需要替换锁文件路径,判断逻辑不变。
先记住:失效缓存等于换一个 key
GitHub Actions 的缓存动作会先查完整 key,没有精确命中时才尝试键前缀和 restore-keys。完全未命中时,如果作业成功,动作会用当前 key 保存新的缓存。已有缓存不能直接改内容,所以“让旧缓存失效”的实际做法是让依赖锁文件的哈希变化,进而生成一个新 key。
因此,一个可维护的缓存键至少包含三类信息:
- 运行环境,例如
runner.os,避免 Linux 和 Windows 共用不兼容的缓存; - 缓存对象名称,例如
npm或go-build,方便从日志判断用途; - 锁文件哈希,例如
hashFiles('**/package-lock.json'),让依赖变化触发新键。
第 1 步:在工作流文件中找到缓存入口
打开仓库的 .github/workflows/,选择实际执行安装依赖的工作流。进入 GitHub 仓库页面时,路径是“Actions → 目标工作流 → Run workflow”或对应运行记录;编辑文件时则是“Code → .github → workflows → 目标 YAML 文件”。先确认缓存步骤位于安装依赖之前,否则缓存恢复就失去了意义。
在编辑器中搜索 actions/cache、setup-node 或 cache:。使用 setup-node 的内置缓存时,仍要检查它实际使用的锁文件;需要自定义缓存目录或回退策略时,再使用显式的 actions/cache 步骤。
此时的可见确认是:缓存步骤在 npm ci、npm install 或构建步骤之前,并且 path 指向包管理器真正使用的目录,例如 Linux runner 上的 ~/.npm。
第 2 步:用锁文件哈希组成精确缓存键
在“编辑工作流文件 → 缓存步骤 → with → key”位置,把固定字符串改成下面这种结构。每一行注释都直接写在 YAML 代码中,方便以后维护时知道它为何存在。
- name: Cache npm downloads
id: cache-npm
uses: actions/cache@v4
with:
# 只缓存 npm 下载内容,不把 node_modules 当成跨环境制品
path: ~/.npm
# 系统、缓存用途和锁文件哈希共同决定精确键
key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
# 先尝试同一系统下的其他 npm 缓存
${{ runner.os }}-npm-
# 最后才回退到系统级前缀,避免误用其他平台内容
${{ runner.os }}-
hashFiles() 的路径是相对工作区的 glob。单仓库项目可以写 package-lock.json;monorepo 或锁文件位于子目录时,用 **/package-lock.json。pnpm 和 Yarn 分别替换为 **/pnpm-lock.yaml、**/yarn.lock;Go 项目则可以使用 **/go.sum。
在“保存并提交 → Actions → 目标运行”后,展开缓存步骤的日志,能看到一条由系统名、缓存用途和哈希组成的 resolved key。图 1 是这一路径的原创界面说明图,用来帮助定位字段,不是实际 GitHub 截图。

如果提交只改了业务源码,锁文件哈希不变,命中原缓存是正常的;如果新增或升级依赖,锁文件哈希改变,就应该看到新的精确键。不要为了“强制刷新”给键拼接每次都变化的时间戳,那会让每次运行都无法复用缓存。
第 3 步:用 cache-hit 判断到底命中了哪一种缓存
很多人只看“缓存步骤成功”,但成功不代表精确命中。给缓存步骤加上 id 后,在下一步读取 steps.cache-npm.outputs.cache-hit,就可以把精确命中和回退命中区分开。
- name: Show cache state
if: always()
run: |
# cache-hit 只有精确 key 命中时才是 true
echo "cache-hit=${{ steps.cache-npm.outputs.cache-hit }}"
# 用短消息帮助运行记录快速区分命中、回退和完全未命中
if [ "${{ steps.cache-npm.outputs.cache-hit }}" = "true" ]; then
echo "Exact cache hit: reuse the lockfile-specific cache"
else
echo "No exact hit: restore-keys may have restored a partial cache"
fi
“Actions → 目标运行 → 展开 Show cache state”就是结果核对入口。判断可以按下面三种情况进行:
| 日志现象 | 实际含义 | 下一步 |
|---|---|---|
cache-hit=true | 完整 key 精确匹配 | 确认锁文件没有变化,继续复用 |
cache-hit=false,但日志显示恢复了前缀缓存 | 没有精确命中,使用了较旧的可兼容前缀 | 让安装步骤补齐新依赖,观察作业是否成功保存新 key |
cache-hit=false,且没有可恢复条目 | 完全 miss | 检查锁文件路径、分支范围和保存条件 |
图 2 把这三个可见状态放进一张原创结果说明图。它只用于说明面板应该看什么,不代表某个仓库真实运行过这些结果。

第 4 步:结果为空或不生效时按四个位置排查
1. hashFiles 路径没有匹配到锁文件
如果 glob 写错,哈希表达式就不能代表真正的依赖文件。检查“Code → 目标锁文件”,确认它在工作区内、文件名大小写一致,并且 checkout 步骤在缓存步骤之前。monorepo 不要把根目录路径想当然地写死,优先使用与仓库结构匹配的 glob。
2. 把 restore-keys 误当成精确命中
restore-keys 是降级策略,不是新的精确 key。它可能恢复最近的旧前缀缓存,所以 cache-hit 仍然是 false。依赖变化后看到旧缓存被恢复并不矛盾,后续安装成功时,当前带新哈希的 key 才会保存成新缓存。
3. 分支或触发事件没有缓存访问范围
缓存有分支范围。当前分支找不到时,动作会按规则尝试默认分支,但不能把其他无关分支的缓存当成全局资源。对于低信任触发器,缓存可能只有读取权限,保存动作会被跳过;这时日志会给出提示,不能只根据“步骤没有报红”判断缓存已经写入。
4. 把敏感文件放进缓存路径
缓存内容可能被有读取权限的人取出,不要把 Token、登录凭据或含密钥的配置放进 path。依赖下载目录适合缓存,包含环境密钥的构建产物不适合缓存。
一份可以直接改造的完整模板
如果你只需要 npm 下载缓存,可以从下面模板开始,再按项目目录改写锁文件 glob。关键是保持“checkout → cache → install → build/test”的顺序。
name: Node CI
on:
push:
branches: [main]
pull_request:
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Cache npm downloads
id: cache-npm
uses: actions/cache@v4
with:
# npm ci 会从这个目录读取下载缓存
path: ~/.npm
# 锁文件变化时产生新键,系统变化时也不会串缓存
key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
# 仅允许同一系统下的旧 npm 缓存作为回退
${{ runner.os }}-npm-
- name: Install dependencies
run: npm ci
- name: Test
run: npm test
提交后先看缓存步骤的 resolved key,再看安装和测试是否成功,最后查看当前运行是否出现保存新缓存的日志。这个顺序能把“键算错”“回退成功但不精确”“作业失败没有保存”三个问题分开。
最后的核对清单
key是否包含正确的runner.os和锁文件hashFiles()?path是否是包管理器实际使用的缓存目录?restore-keys是否从具体到宽泛排列,并且没有跨系统回退?- 是否通过
cache-hit区分了精确命中和前缀回退? - 依赖变化后是否看到新 resolved key,而不是用时间戳强制制造每次新键?
- 是否避免把 Token、凭据和其他敏感文件放入缓存路径?
把锁文件哈希放进精确键、把回退键限制在兼容范围内,再用 cache-hit 和运行日志核对结果,基本就能解释“为什么缓存为空”以及“为什么改了依赖却没有失效”。
-
Golang · Go教程 | 4个月前 | CI/CD · gitHub actions · Go教程 · 持续集成 · Go 持续集成 CI Go test GitHub Actions self-hosted runner 自托管 runner340 收藏
-
351 收藏
-
196 收藏
-
253 收藏
-
395 收藏
-
文章 · 软件教程 | 45分钟前 | 软件教程 · Intellij IDEA Name JetBrains IDE File and Code Templates 文件模板 New菜单 PACKAGE_NAME175 收藏
-
420 收藏
-
350 收藏
-
162 收藏
-
487 收藏
-
345 收藏
-
306 收藏
-
文章 · 软件教程 | 1小时前 | 软件教程 · Docker Compose Profiles COMPOSE_PROFILES config --profiles config --services500 收藏
-
462 收藏
-
文章 · 软件教程 | 1小时前 | yaml · 性能 · gitHub actions · 批处理 缓存键 package-lock.json GitHub Actions restore-keys hashFiles cache-hit142 收藏
-
420 收藏
-
462 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 485次学习