
结论:GitHub Actions 缓存失效的4种真实原因,附可复现的配置方案
GitHub Actions 跑 CI 时,依赖缓存总是莫名其妙 miss。明明没改 requirements.txt,缓存却废了;或者改了注释,缓存全失效。本文从踩坑出发,梳理4种最常见的缓存失效场景,逐一给出可落地的解决方案。覆盖 actions/cache v3 和 v4 的行为差异、restore-keys 的坑、跨 runner 路径问题和 lock 文件格式漂移。
场景 1:requirements.txt 改了,但只改了注释
你以为不改依赖版本缓存就安全?不一定。
actions/cache v3 对 requirements.txt 的处理是:只 hash 文件的前 1MB 内容。如果你的 requirements.txt 超过 1MB(常见于大项目),文件末尾的内容完全不参与 hash 计算。
更隐蔽的是 v3 和 v4 的差异:v3 会在缓存 miss 时自动 fallback 到 restore-keys 做前缀匹配,但 v4 默认 restore-only=true,只恢复不保存。如果你手动设置了 save: false,缓存永远不会被写入。
# v3 的行为(隐式 fallback)
- uses: actions/cache@v3
with:
path: ~/.cache/pip
key: pip-${{ runner.os }}-${{ hashFiles('requirements.txt') }}
# v4 的行为(必须显式 save)
- uses: actions/cache@v4
with:
path: ~/.cache/pip
key: pip-${{ runner.os }}-${{ hashFiles('requirements.txt') }}
save: always # 默认 true,但显式写出来更安全
restore-keys: |
pip-${{ runner.os }}-
场景 2:restore-keys 和 key 重复导致缓存覆盖
restore-keys 的设计是在精确 key miss 时做前缀匹配降级,但写错会适得其反。
restore-keys 包含了精确 key 的完整前缀时,每次 restore 都可能覆盖已有的精确缓存。
# 错误写法:restore-keys 包含了精确 key 的完整前缀
- uses: actions/cache@v4
with:
path: ~/.cache/pip
key: pip-${{ runner.os }}-${{ hashFiles('requirements.txt') }}
restore-keys: |
pip-${{ runner.os }}-${{ hashFiles('requirements.txt') }} # 这里!完全重复
pip-${{ runner.os }}-
# 正确写法:restore-keys 只保留公共前缀
- uses: actions/cache@v4
with:
path: ~/.cache/pip
key: pip-${{ runner.os }}-${{ hashFiles('requirements.txt') }}
restore-keys: |
pip-${{ runner.os }}-
场景 3:跨 runner 类型导致路径不一致
GitHub Actions 的 runner 有 ubuntu、windows、macos 三种类型,缓存路径完全不同。跨类型使用同一个 key,缓存必然 miss。
# 用 hashFiles 生成跨平台 key 时必须包含 OS
key: pip-${{ runner.os }}-${{ hashFiles('**/requirements.txt') }}
# Windows 路径是 ~/AppData/Local/pip/cache
# Linux 路径是 ~/.cache/pip
# macOS 路径是 ~/Library/Caches/pip
# 所以 key 里必须包含 runner.os
另一个坑:同一 OS 不同版本(如 ubuntu-20.04 和 ubuntu-22.04)系统包不同,混用也会导致缓存失效。如果你的 CI 需要多版本测试,建议用矩阵策略分开管理缓存。
场景 4:依赖 lock 文件未提交或格式漂移
很多人用 pip-compile 生成 requirements.txt,但 lock 文件未提交到仓库,或者每次 pip-compile 生成的格式有微小差异(如同一个依赖的版本约束写法不同),导致 hash 不一致。
# requirements.txt 每次 pip-compile 可能产生格式差异
# 比如 "requests>=2.28.0" vs "requests>=2.28.1"(实际没更新,只是写法变了)
# 解法:用 pip-compile --generate-hashes 锁定精确版本
# 生成的 requirements.txt 包含每个包的 sha256 hash
# 这样 hashFiles() 的结果完全可复现
# 示例输出:
# requests==2.31.0 \
# --hash=sha256:58cd2187c01e6596226b4
# --hash=sha256:a1227c73a01266ef67b8fbe485cd8a
验证方法:本地跑两遍 pip-compile,对比生成的 hash 是否完全一致。如果不一致,检查是否用了 --resolution=highest 导致版本浮动。
实战配置(v4 + pip-compile)
jobs:
ci:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Generate requirements.txt
run: pip install pip-tools && pip-compile --generate-hashes --output-file=requirements.txt
- name: Cache pip dependencies
uses: actions/cache@v4
with:
path: |
~/.cache/pip
~/.local/cache/pip
key: pip-${{ runner.os }}-${{ hashFiles('requirements.txt') }}
restore-keys: |
pip-${{ runner.os }}-
- name: Install dependencies
run: pip install -r requirements.txt
- name: Run tests
run: pytest tests/
关键点:key 里必须包含 OS 标识(如 ubuntu),否则跨 runner 时缓存必 miss。restore-keys 只保留公共前缀 pip-ubuntu-,避免覆盖精确匹配。
现在你可以做什么
检查自己的 CI 配置中是否有上述 4 种问题:v3 升级到 v4 并显式设置 save 行为;restore-keys 确保只保留公共前缀不要重复;key 里包含 OS 标识防止跨 runner miss;用 pip-compile --generate-hashes 替代裸 requirements.txt。
更多交流点击入群






