CI のキャッシュキーは OS だけでなく CPU アーキテクチャも含める
GitHub Actions などの CI で node_modules や依存物のキャッシュキーを「runner の OS + lockfile ハッシュ」だけで構成すると、x64 と arm64 の runner が混在した時点で壊れる。runner.os は x64 でも arm64 でも同じ Linux を返すため、両アーキのジョブが同一キャッシュを共有し、先にキャッシュを保存したジョブのアーキ用ネイティブバイナリだけが残る。
症状は、後から走った別アーキのジョブで napi 系パッケージ(rolldown、swc、esbuild など optionalDependencies でアーキ別バイナリを配布するもの)が「Cannot find native binding」「Cannot find module(binding-linux-arm64-gnu 等のアーキ名入りパッケージ)」で起動失敗する。lockfile には全アーキの binding が登録されていても、install 実行時のアーキ分しか node_modules に入らないため、キャッシュ復元では補完されない。
再発条件: (1) 複数 workflow / job が同じキャッシュキーで同じ node_modules パスをキャッシュしている、(2) その中に異なるアーキの runner が混在している(一部ジョブだけ arm runner に移行した場合が典型)、(3) キャッシュは先勝ちなので、どちらのアーキが壊れるかは実行順に依存し flaky に見える。
診断手順: エラーに出るバイナリパッケージ名のアーキと runner のアーキを突き合わせる → lockfile に当該アーキの optional 依存が登録されているか確認 → 同じキャッシュキーを使う全 workflow の runs-on を横断して grep し、アーキ混在がないか確認する。
予防策: キャッシュキーに runner.os に加えて runner.arch(相当の変数)を必ず含める。runner の一部だけを arm に移行するときは、そのジョブが共有しているキャッシュキーの棚卸しをセットで行う。