GitHub Actionsのcache設定で依存不整合を起こすPRをどうレビューするか

GitHub Actionsでは、依存パッケージやビルド中間物をキャッシュするとCI時間を短縮できる。
しかしキャッシュは、速くなる一方で 古い依存や別条件の成果物を混ぜる入口 にもなる。

レビューで危ないのは、次のような設定だ。

  • package-lock.jsonpnpm-lock.yaml をcache keyに含めていない
  • restore-keys が広すぎて別ブランチや別依存のキャッシュを拾う
  • node_modules をそのままキャッシュして依存解決を曖昧にしている
  • OS、Node.js version、package managerの差分がkeyに入っていない
  • キャッシュヒット時にclean install相当の検証がなくなる

この記事ではGitHub Actionsの高速化ではなく、
キャッシュがCIの再現性を壊していないかをレビューで確認する観点を整理する。

まず止めたい設定

lockfileを見ないcache設定
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
Comment
@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を含める。

lockfileを含むcache key
- uses: actions/cache@v4
  with:
    path: ~/.npm
    key: npm-${{ runner.os }}-node20-${{ hashFiles('package-lock.json') }}
    restore-keys: |
      npm-${{ runner.os }}-node20-
Comment
@Reviewer: lockfileのhashがcache keyに含まれているため、依存解決が変わったPRでは別キャッシュになります。restore-keysが広すぎないかも合わせて確認してください。

2. restore-keys が広すぎないか

restore-keys は便利だが、広すぎると「似ているが違う」キャッシュを拾う。
特に npm- だけのようなkeyは、OSやNode.js versionの差分まで混ざりやすい。

Comment
@Reviewer: `restore-keys` が `npm-` まで広がっており、Node.js versionやOSが異なるキャッシュを復元する可能性があります。少なくともOS、runtime version、package manager単位で境界を残してください。

復元候補を広げるほど速くなる可能性はある。
その代わり、再現性の説明責任も大きくなる。

3. 何をキャッシュしているかが妥当か

node_modules を直接キャッシュすると、削除された依存やpostinstallの結果が残りやすい。
多くの場合は、package managerのダウンロードキャッシュを復元し、installコマンドで依存を再構成する方が読みやすい。

npmキャッシュを使い、installは毎回行う例
- uses: actions/setup-node@v4
  with:
    node-version: 20
    cache: npm

- run: npm ci
Comment
@Reviewer: `node_modules` を直接復元する構成ではなく、package managerのキャッシュを使って `npm ci` を毎回実行しているため、lockfileに基づく再現性を保ちやすいです。

レビューでは、キャッシュ対象が「再生成できる補助データ」なのか「実行に使う成果物そのもの」なのかを分けて見る。

改善例

依存キャッシュを使いながら、lockfileを正にする。

依存再現性を保つcache設定
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/cachedist 相当を復元する場合、環境変数、feature flag、生成元ファイルの差分が混ざる。

Comment
@Reviewer: ビルド成果物をキャッシュしていますが、keyにソース差分やビルド条件が十分に含まれていません。成果物キャッシュを使うなら、入力条件をkeyで表現するか、PR検証では依存キャッシュに限定してください。

成果物キャッシュは、入力条件を説明できる場合だけ通しやすい。
説明できないなら、まず依存キャッシュだけに留める方がレビューしやすい。

レビュー観点チェックリスト

GitHub Actions cacheレビューの確認項目
  • 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では、その前提が崩れていないかをレビューで止める。