mainのnode_modules symlinkをやめ、worktreeごとに依存を入れる
今回やったこと
Git worktreeで自動処理を実行するとき、mainリポジトリのnode_modulesをsymlinkで共有する構成をやめ、各worktreeの中で依存をインストールする共通処理へ寄せた。対象は8本の自動処理で、依存の再利用による短縮より、実行単位ごとの再現性を優先した。
symlinkは一見すると速い。既にインストール済みの依存をそのまま参照できるからだ。しかし、あるサイトで依存が追加されたとき、別のworktreeが想定している状態とmain側の実体がずれる。実行側は新しい依存を要求しているのに、参照先は古いまま、という組み合わせが起きると、エラーは原因ではなく別の場所を指す。自動ジョブでは、失敗を再現できないこと自体が大きな障害になる。
依存をworktreeの中へ閉じ込める
共通シェル関数npm_install_worktreeに、依存を用意する手順を集約した。引数は対象ディレクトリとログ用タグ。まずnpmの存在とpackage.jsonを確認し、lockfileがあればnpm ciを使う。ciが失敗した場合だけnpm installで再試行し、最後にnode_modulesディレクトリの存在まで確認する。
. "$REPO/scripts/lib/npm_worktree.sh"
npm_install_worktree "$WT/sites/site-directory" "site-publish" \
|| { echo "dependency setup failed"; exit 1; }
lockfileがある場合にnpm ciを優先するのは、package.jsonの現在値を解決し直すのではなく、固定された依存関係を再現するためだ。lockfileが無い場合はnpm installを使う。どちらもグローバルなnpmキャッシュを--prefer-offlineで活用するが、参照先の実体はworktree内になる。
エラーを黙らせない
依存インストールのログは一時ファイルへ保存し、--silentは使わない。無人ジョブでは終了コードだけが残っても、原因が分からなければ復旧できない。失敗したときはタグ、対象ディレクトリ、終了コード、ログ末尾を出す。npm ciからnpm installへ切り替えた場合も、その事実を警告として残す。
処理の結果は次のように分かれる。
| 確認 | 失敗時の扱い |
|---|---|
npmがPATHにあるか | 終了コード127で中止 |
package.jsonがあるか | 終了コード2で中止 |
| 依存インストールが成功したか | ログ末尾を出して中止 |
node_modulesが生成されたか | 成功扱いにしない |
依存が用意できないままビルドへ進めると、後段で別のエラーになる。そこで、この関数が非ゼロを返したら呼び出し側は自動処理を中止する。原稿やデプロイ物を作るジョブでは、検証できない状態の成果物を後へ流さないことが優先される。
共有より再現性を選ぶ場面
worktreeは同じリポジトリの別作業場所だが、実行時の依存まで共有する必要はない。コードの差分と依存の解決結果を同じ作業単位に置くことで、「どのworktreeで、どのpackage-lock.jsonを使い、何をインストールしたか」を追跡できる。ディスク容量やインストール時間は増えるが、キャッシュを使えば毎回すべてをネットワークから取り直す必要はない。
この判断は、すべての環境でsymlinkが悪いという話ではない。依存が頻繁に変わる複数レーンを無人で動かし、失敗時に原因を追跡する必要がある場合に、共有の便利さより隔離の価値が上回る。自動処理では、速く開始できることより、同じ入力から同じ前提で起動できることが重要になる。
実装ステータスと更新履歴
実装済み・稼働中。worktree内の依存分離を共通処理として運用している。
- 更新履歴: 2026-09-11 初稿。
- 訂正履歴: なし。