GitHub Actionsのcache設定で依存不整合を起こすPRをどうレビューするか
GitHub Actionsのcache設定で依存不整合を起こすPRをどうレビューするか
GitHub Actionsでは、依存パッケージやビルド中間物をキャッシュするとCI時間を短縮できる。
しかしキャッシュは、速くなる一方で 古い依存や別条件の成果物を混ぜる入口 にもなる。
レビューで危ないのは、次のような設定だ。
package-lock.jsonやpnpm-lock.yamlをcache keyに含めていないrestore-keysが広すぎて別ブランチや別依存のキャッシュを拾うnode_modulesをそのままキャッシュして依存解決を曖昧にしている- OS、Node.js version、package managerの差分がkeyに入っていない
- キャッシュヒット時にclean install相当の検証がなくなる
この記事ではGitHub Actionsの高速化ではなく、
キャッシュがCIの再現性を壊していないかをレビューで確認する観点を整理する。
まず止めたい設定
name: test
on:
pull_request:
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/cache@v4
with:
path: node_modules
key: npm-cache
restore-keys: |
npm-
- run: npm install
- run: npm test@Reviewer: cache keyが固定値のため、lockfileが変わっても古い依存キャッシュを再利用します。依存関係の再現性を担保するため、lockfileのhashをkeyに含めてください。この設定は、CIが速くなることもある。
しかし、依存関係が変わっても同じ node_modules を復元するため、PRの差分とCI実行環境が一致しない。
レビューでは、キャッシュヒット率だけではなく、
キャッシュが外れても通る構成か、ヒットしても正しい依存になる構成かを見る必要がある。
なぜ危ないのか
CIキャッシュの事故は、テスト失敗よりも分かりにくい。
古い依存が残ったままテストが通ると、レビューアーはPRが安全だと誤認する。
起きやすい問題は次の通りである。
- lockfile変更がCIに反映されない
- ローカルでは再現しないテスト結果になる
- Node.js versionやOS差分をまたいでキャッシュが復元される
- 削除したパッケージが残り、不要な依存に気づけない
- キャッシュが壊れた時だけ原因不明のCI失敗になる
キャッシュは最適化であって、依存解決の代替ではない。
レビューでは、cache keyが依存の契約を表現しているかを確認したい。
レビューで見たい3つの判断線
1. lockfileをcache keyに含めているか
依存の実体を決めるのは package.json だけではない。
実際の解決結果はlockfileに固定されるため、cache keyにもlockfileのhashを含める。
- uses: actions/cache@v4
with:
path: ~/.npm
key: npm-${{ runner.os }}-node20-${{ hashFiles('package-lock.json') }}
restore-keys: |
npm-${{ runner.os }}-node20-@Reviewer: lockfileのhashがcache keyに含まれているため、依存解決が変わったPRでは別キャッシュになります。restore-keysが広すぎないかも合わせて確認してください。2. restore-keys が広すぎないか
restore-keys は便利だが、広すぎると「似ているが違う」キャッシュを拾う。
特に npm- だけのようなkeyは、OSやNode.js versionの差分まで混ざりやすい。
@Reviewer: `restore-keys` が `npm-` まで広がっており、Node.js versionやOSが異なるキャッシュを復元する可能性があります。少なくともOS、runtime version、package manager単位で境界を残してください。復元候補を広げるほど速くなる可能性はある。
その代わり、再現性の説明責任も大きくなる。
3. 何をキャッシュしているかが妥当か
node_modules を直接キャッシュすると、削除された依存やpostinstallの結果が残りやすい。
多くの場合は、package managerのダウンロードキャッシュを復元し、installコマンドで依存を再構成する方が読みやすい。
- uses: actions/setup-node@v4
with:
node-version: 20
cache: npm
- run: npm ci@Reviewer: `node_modules` を直接復元する構成ではなく、package managerのキャッシュを使って `npm ci` を毎回実行しているため、lockfileに基づく再現性を保ちやすいです。レビューでは、キャッシュ対象が「再生成できる補助データ」なのか「実行に使う成果物そのもの」なのかを分けて見る。
改善例
依存キャッシュを使いながら、lockfileを正にする。
name: test
on:
pull_request:
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: npm
- run: npm ci
- run: npm testこの構成なら、レビューアーは次を確認しやすい。
- Node.js versionが明示されている
- lockfileに基づく
npm ciが毎回実行される - キャッシュはinstall高速化の補助に留まっている
node_modulesの残存状態に依存していない- CIがキャッシュなしでも成立する
ビルド成果物のキャッシュはさらに慎重に見る
依存キャッシュより危ないのが、ビルド成果物のキャッシュである。
たとえば .next/cache や dist 相当を復元する場合、環境変数、feature flag、生成元ファイルの差分が混ざる。
@Reviewer: ビルド成果物をキャッシュしていますが、keyにソース差分やビルド条件が十分に含まれていません。成果物キャッシュを使うなら、入力条件をkeyで表現するか、PR検証では依存キャッシュに限定してください。成果物キャッシュは、入力条件を説明できる場合だけ通しやすい。
説明できないなら、まず依存キャッシュだけに留める方がレビューしやすい。
レビュー観点チェックリスト
- lockfileのhashがcache keyに含まれているか
- OS、runtime version、package managerの差分がkeyに入っているか
restore-keysが広すぎないかnode_modulesなど実行対象そのものを直接キャッシュしていないか- installコマンドがlockfileを正として毎回実行されるか
- キャッシュがなくてもCIが通る構成か
- ビルド成果物キャッシュの入力条件がkeyで説明されているか
まとめ
GitHub Actionsのキャッシュは、CIを速くするための道具であって、依存解決を省略するための道具ではない。
レビューでは、cache keyが依存と環境の境界を表現しているかを確認したい。
キャッシュで速くなったCIより、キャッシュが壊れても正しく失敗できるCIの方が信頼できる。
高速化のPRでは、その前提が崩れていないかをレビューで止める。