git worktreeは複数の作業ディレクトリを同時に持てる並行開発の定番の仕組みだ。ただし共有ドライブ(SMB)上でpnpm installを同時実行すると、SMB特有の弱点でハングしうる。原因の仕組みと現実的な回避策を整理する。
git worktreeとは: 1つのリポジトリで複数の作業ディレクトリを持つ仕組み
git worktreeは、同一のGitリポジトリに対して複数の作業ディレクトリを追加できるコマンドだ。通常のGitリポジトリは.gitディレクトリ1つに対して作業ディレクトリも1つだが、git worktree addを使うと、同じ履歴・同じリモート設定を共有したまま、別のパスに別ブランチをチェックアウトした作業ディレクトリを作れる。
# メインの作業ディレクトリ(例: main ブランチ)はそのまま
git worktree add ../feature-a feature-a
git worktree add ../feature-b feature-b
git worktree list
git公式ドキュメントによれば、同じブランチを複数のworktreeで同時にチェックアウトすることは--forceを付けない限りできない仕様になっている(git-worktree 公式ドキュメント、2026-04-20リリースのGit 2.54.0時点)。同じ文書のBUGSセクションには、サブモジュールを含むリポジトリでの複数チェックアウトは「実験的で不完全」とも明記されている。ブランチごとに独立したチェックアウト先を保証する代わりに、同じブランチの二重チェックアウトだけは仕組み上ブロックされる、という設計だ。
なぜ並行して複数のworktreeを使うのか
worktreeを使う一番の動機は、git stashやgit checkoutを挟まずに、複数のタスクを同時に手元に置いておけることだ。1つの作業ディレクトリを使い回していると、別タスクに切り替えるたびにビルド成果物や未コミットの変更を退避する手間が発生するが、worktreeを分ければ、それぞれのディレクトリで独立にファイルを触れる。
私も、並行して着工していた2つの開発タスク向けに、それぞれ専用のworktreeを切って作業していたことがある。タスクごとに依存パッケージのバージョンが微妙に違う場面もあり、環境ごと分けておきたいときは特に相性がいい。
複数タスクを同時に走らせるときの工数管理という観点では、以前AIエージェントの工数見積もりをPSP手法で運用してみた記事でも書いたが、並行作業はタスクの切り分け自体は楽になる一方、環境構築のコストは並行本数分だけ素直に積み上がる。今回踏んだ落とし穴は、まさにその環境構築フェーズで起きた。
共有ドライブ(SMB)でpnpm installが詰まった: 実際に何が起きたか
問題が起きたのは、リポジトリを共有ドライブ(SMB)上に置いて運用していた環境だった。2つの開発タスクに対応する2本のworktreeを同じ共有ドライブ上に作り、それぞれのディレクトリでpnpm installを同時に走らせたところ、尋常でないほど長い時間ハングする状態になった。ターミナルはどちらも進捗らしい進捗を表示しないまま、長時間応答がない状態が続いた。
結局、片方のインストールを止めて仕切り直すことになった。ローカルSSD上の同様の環境では起きたことのない現象で、原因を追ってみると、共有ドライブ特有のI/O特性がボトルネックになっていることが分かった。
原因はSMBプロトコルの構造的な弱点: 小ファイルI/Oとpnpmのハードリンク/アトミックrename
先ほどのハングは偶然ではない。原因は1つではなく、pnpmのインストール方式とSMBプロトコルの特性が重なって起きている。以下はpnpm v9系での挙動を基準にした整理だ。
pnpmはnode_modulesをハードリンクで構成する。 pnpm公式ドキュメントによれば、node_modules配下の全ファイルはcontent-addressable store、つまりグローバルなパッケージ格納庫へのハードリンクとして構成され、ネストした依存関係の構造自体はシンボリックリンクで組み立てられる(pnpm公式: symlinked node_modules structure)。ハードリンクは同一ファイルシステム内でしか機能しないのが前提で、ネットワークファイルシステム(SMB/CIFS)はハードリンクのサポートが不完全、または実装依存であるとコミュニティや技術ブログでたびたび報告されている。
SMBは小ファイルの大量I/Oに構造的に弱い。 Microsoft公式のトラブルシューティング文書によれば、SMBはファイルを1つ作成するたびに複数のプロトコル操作を必要とし、1MB未満の小さいファイルが数百〜数百万個ある場合はスループットが大きく落ちる(Microsoft Learn: SMB経由のファイル転送が遅い)。node_modulesは典型的にこの「小ファイルが大量にある」ディレクトリで、パッケージ数が数百を超えるプロジェクトでは数万ファイル規模になることも珍しくない。個人ブログの実測ベンチマークでも、macOSのSMB実装は数万ファイル規模の小ファイルワークロード向けに最適化されておらず、1000ファイルのディレクトリlistingがチューニング前は42.3秒、チューニング後でも4.4秒かかり、9.6倍の改善にとどまったという報告がある(yigitkonur.com: NFS is faster than SMB for Mac dev envs)。
pnpmのアトミックrenameがSMB上で失敗しうる。 pnpmはパッケージを展開するとき、まず_tmp_<PID>のような一時ディレクトリに解凍し、完了後にrename(2)でアトミックに確定ファイル名へ置き換える。私が実務で見た別プロジェクトのセットアップスクリプトでも、SMBマウント上でこのrename(2)がEACCESで拒否されるケースが実際にコメント付きで対処されており、これがinstall失敗・ハングの引き金の1つになりうることを裏付けている。
この3つが重なると、SMB越しのnode_modules展開は「小ファイルを大量に作成する処理」自体が遅い上に、ハードリンクとrenameという仕組みの前提そのものがネットワークファイルシステムと噛み合わない。これを2つのworktreeで同時に走らせれば、同一の共有ドライブに対するI/O要求が競合し、単独実行よりもさらに状況が悪化するのは構造上自然な帰結だ。
3つの回避策を比較する
原因の構造が分かれば、回避策の方向性も見えてくる。回避策は大きく3つあり、速度・安全性(確実に動くか)・環境依存度の3軸で比較した。
| 回避策 | 速度 | 安全性 | 環境依存度 |
|---|---|---|---|
| ①worktree自体をローカルディスクに切る | 速い(ローカルSSD相当) | 高い(ファイルシステムの前提を満たす) | 低い |
②node_modulesだけローカルにシンボリックリンクで退避する |
速い | 中(symlink管理の手間が増える) | 高い(次項で詳述) |
| ③複数worktreeのinstallを直列化する | 遅い(並列分の待ち時間がそのまま乗る) | 高い(確実に動く) | 低い |
①が最も手堅い。 worktree自体を/tmpやローカルSSD配下に作り、共有ドライブ側にはソースコードの参照だけを置く構成にすれば、node_modulesの展開はローカルファイルシステム内で完結する。ハードリンクもrenameも問題なく機能する前提に戻せるので、根本的な解決になる。
③は最も確実だが遅い。 同じ共有ドライブ上でも、installを1本ずつ順番に実行すれば競合は起きない。並列化のメリットは失うが、追加の設定や検証が不要ですぐ実践できる。
②は速度は出るが環境依存の注意点がある。 詳細は次のセクションで扱う。
このほかに、rename失敗そのものへの備えとして、installがEACCES等で失敗したらリトライし、中途半端に残った一時ディレクトリを掃除してから再実行するという補助的なテクニックもある。私が見た別プロジェクトのセットアップスクリプトでは、この仕組みが実装されていた。ただしこれはI/O競合そのものを解消するものではなく、renameの失敗に対する耐性を上げる技術なので、①〜③の代替にはならない。
回避策②(シンボリックリンク退避)は環境依存であることに要注意
②の「node_modulesをローカルにシンボリックリンクで逃がす」は、共有ドライブの遅さを避けつつworktree自体は共有ドライブ上に残せる、一見都合の良い折衷案に見える。ただし、macOSのSMBクライアントはシンボリックリンクの挙動がSMBバージョンやOSバージョンに依存し、常に確実に機能するとは限らない。
2016年に検証されたある技術ブログでは、OS X 10.11.6でSMBv3を使っている環境でシンボリックリンクされたディレクトリが空に見える不具合が報告されており、当時知られていた回避策(smb1_only指定)も、その後のSierra 10.12.3アップデートで機能しなくなったと追記されている(blog.mayer.tv: SMB symlink on OS X)。記事自体は古いが、「あるバージョンで動いた回避策が、次のOSアップデートで動かなくなる」という構造は今のmacOSでも起こりうる。
つまり②を採用する場合は、一度どこかで見た回避策だからと信用するのではなく、自分の手元のOSバージョン・SMBバージョン・マウントオプションで実際に動くかを事前に検証することが前提になる。万能策として扱わず、動作確認を挟んでから採用してほしい。私自身はこの回避策をまだ試していないので、採用する前に必ず自分の環境で事前検証することを強く勧める。
まとめ
git worktreeは、複数タスクを同時に手元へ置いておきたいときに便利な仕組みだが、リポジトリを共有ドライブ(SMB)上に置いている場合、pnpm installの並列実行はI/O競合の温床になる。原因は、pnpmのハードリンク+アトミックrenameという実装の前提と、SMBの小ファイルI/Oに対する構造的な弱さが噛み合っていないことにある。
一番堅いのは、worktree自体をローカルディスクに切ってしまうことだ。共有ドライブでの運用にこだわるなら、installの直列化が次善策になる。シンボリックリンクでの退避は速度が出る一方、環境依存の不具合報告がある以上、万能策としては扱えない。
残っている検証課題は2つある。Windows上のSMBクライアントでも同様のI/O競合が発生するか、そしてpnpm v10系で一時ディレクトリの命名規則が変わった場合にrenameの挙動がどう変化するか。どちらも手元ではまだ未検証なので、機会があれば追って確認したい。
よくある質問
Q: git worktreeと共有ドライブ(SMB)を組み合わせるときに何に気をつければいいですか?
A: 複数のworktreeでpnpm installのような大量の小ファイルI/Oを伴う処理を、同一のSMBマウント上で同時に実行しないことだ。worktree自体をローカルディスクに切る、またはinstallを直列化することで回避できる。
Q: node_modulesをSMB上に置くとなぜ遅くなるのですか?
A: pnpmはnode_modules内の全ファイルをcontent-addressable storeへのハードリンクとして構成するが、SMBは1ファイル作成のたびに複数のプロトコル操作を要するため、小ファイルが大量にあるnode_modulesの展開が構造的に遅くなる。macOSのSMB実装での実測では、1000ファイルのディレクトリlistingが42.3秒(チューニング後4.4秒)かかったという報告もある(出典)。
Q: 3つの回避策のうちどれを選べばいいですか?
A: 一番手堅いのはworktree自体をローカルディスクに切ることだ。共有ドライブでの運用を維持したいならinstallの直列化が確実。シンボリックリンクでの退避は速いが、macOSのSMBクライアントでOSバージョンによって挙動が変わった実例があるため、事前に手元の環境で検証してから採用してほしい。
Q: WindowsやLinuxでも同じ問題は起きますか?
A: SMB/CIFSプロトコル自体の設計に起因する部分があるため理論上は起こりうる。ただし今回参照した実測報告はmacOSのSMBクライアントに関するものが中心で、Windows/Linux環境での再現性は確認できていない。
