# pnpmとWindowsが、このブログのビルドを2度壊した — シャドウ化したChocolateyインストールと、ブロックされたシンボリックリンク

> pnpmのバージョンスイッチャーは、シャドウ化したChocolateyインストールの裏でキャッシュを破損させました。Windowsは別途、ビルドが依存関係をシンボリックリンクするのをブロックしました。

- Source: https://oharu121.com/ja/blog/pnpm-version-switcher-enoent-chocolatey-shadowed-path/
- Published: 2026-08-22T12:05:09+09:00
- Updated: 2026-08-23T04:54:40+09:00
- Tags: pnpm, 開発ツール

---
**要点**

- pnpm自身のセルフマネージドなバージョンスイッチャーは、`package.json`の`packageManager`フィールドによって起動され、ピン留めされたバージョンのダウンロード途中で失敗し、どのコマンドに対しても一切動かなくなることがあります。
- これはたまたまでも、古いインストールの問題でもありません。複数の公開されたpnpmのissueが、Dockerイメージ、CIランナー、他人のマシンといった互いに無関係な環境から、まったく同一のENOENT失敗を報告しています。
- pnpmを再インストールすれば壊れたキャッシュは直りますが、同じツールの忘れられた2つ目のインストールがシェルのPATH上でより先に座っていると、修正が不完全に見えることがあります。
- `which pnpm`は、最初の診断の時点ですでにその2つ目のインストールを表に出していました。ただ、壊れて見える行ではなかったため、読み飛ばされました。
- 動作するインストールを`PATH`で先頭に置いたことで直ったのは、それを実行したシェルだけで、それより先には及びませんでした。翌日、別のシェルで同一のエラーが再発しました。原因はChocolateyのインストールがまだ存在していて、順位を下げられただけで削除されてはいなかったことです。
- 別のビルドが、後になって`EPERM`エラーでシンボリックリンクに失敗しました。原因はWindowsが標準アカウントに対してデフォルトでシンボリックリンクの作成をブロックしていることで、開発者モードを有効にすれば直り、昇格されたシェルは不要です。

## はじめに

このリポジトリにある別の記事のために新しいサムネイル画像を正規化していたとき、`pnpm thumbnails:fix`が完全に壊れました。単なる通常のコマンド失敗ではなく、pnpmがどのシェルでもどのコマンドに対しても一切動かなくなるという壊れ方でした。これにより、このブログ自身のパイプラインがドラフトをチェック済みとみなす前に要求する検証ゲートを含め、後続のすべてがブロックされました。最初に思いついたのは古い、あるいは壊れたインストールという説明でしたが、その推測は示唆に富む形で外れていました。**実際の原因は、pnpm自身のセルフマネージドなバージョン切り替え機能が、自分自身のインストールを完了できずにいたことでした**。それに加えて、たった今修復したばかりのコピーよりも先にあるシェルのPATH上に、忘れられていた2つ目のpnpmインストールが座っていたことも重なりました。本記事では、そのセルフスイッチの失敗がどう見えるか、それがこのマシン特有の問題ではなく既知の上流バグである証拠、シャドウ化したインストールがどのように一行で済むはずの修正を複数ステップの診断に変えたか、その修正がなぜ次のセッションまで持たなかったか、そして同じマシンで後日、別の記事を公開している最中に遭遇した、もう1つの無関係なWindowsの失敗、すなわちビルドが自身の依存関係のシンボリックリンク作成をブロックされた件について整理します。

## スイッチャー自体が壊れていた

`pnpm thumbnails:fix`は、裏側のスクリプトを一行も実行しないうちに失敗しました。

```
[WARN] Unsupported engine: wanted: {"node":">=24"} (current: {"node":"v22.12.0","pnpm":"11.3.0"})
 ERROR  Failed to switch pnpm to v11.3.0. Looks like pnpm CLI is missing at "C:\Users\<user>\AppData\Local\pnpm\.tools\@pnpm+win-x64\11.3.0\bin" or is incorrect
spawnSync C:\Users\<user>\AppData\Local\pnpm\.tools\@pnpm+win-x64\11.3.0\bin\pnpm ENOENT
```

このリポジトリの`package.json`は`"packageManager": "pnpm@11.3.0"`を固定しています。最近のpnpmはこのフィールドを自分で読み取り、指定されたバージョンがユーザーごとのツールキャッシュにまだなければ、その場でダウンロードしてインストールしてから、そちらへ再実行します。**このキャッシュは`PNPM_HOME/.tools/@pnpm+win-x64/<version>/`にあり、これはpnpmが自分自身のバージョンを管理しているのであって、Corepackのような別のツールがやっているわけではありません。**

`11.3.0`のキャッシュフォルダ自体は存在していました。ただ`bin`サブフォルダを含んでいなかっただけです。

```
total 15
drwxr-xr-x 1 <user> 197609   0 Aug 22 10:11 .
drwxr-xr-x 1 <user> 197609   0 Aug 22 10:11 ..
drwxr-xr-x 1 <user> 197609   0 Aug 22 10:11 node_modules
-rw-r--r-- 1 <user> 197609  43 Aug 22 10:11 package.json
-rw-r--r-- 1 <user> 197609 438 Aug 22 10:11 pnpm-lock.yaml
-rw-r--r-- 1 <user> 197609  39 Aug 22 10:11 pnpm-workspace.yaml
```

**セルフインストールが実行ファイルを置く前に書き込むはずのものはすべて揃っていましたが、そのインストールを使えるものにする1つのファイルだけが欠けていました。** `pnpm --version`を再実行しても同じエラーが再現され、これにより一時的なネットワークの不調である可能性は排除されました。セルフマネージドなスイッチャーは、自分では回復できない状態に自身のキャッシュを残してしまっており、以降どの物理的なpnpmバイナリから呼び出されても、すべての呼び出しが同じ欠けたパスに突き当たりました。

## たまたまでも、このマシンのせいでもない

この時点での直感は、インストール自体を疑うことでした。その直感のまま動く前に軽く検索してみたところ、診断は変わりました。**このエラーは一言一句そのまま、互いに何の共通点もない環境の間で繰り返し報告されています。**RenovateのDockerイメージ、Render.comのデプロイ、そしていくつかの独立したプロジェクトが、いずれも同一の`Failed to switch pnpm to v<X>. Looks like pnpm CLI is missing... ENOENT`というメッセージを報告しており、これは複数のポイントリリースにまたがる、pnpmのオープンなissueで追跡されています。

**壊れやすいのはpnpm自身のセルフマネージドなバージョンスイッチャーであって、pnpmの特定のインストール方法ではありません。**Dockerイメージ、CIランナー、そして個人のWindowsマシンは、インストール方法もOSも履歴も何一つ共有していませんが、それでも同じ文言の同じ失敗にぶつかります。それは、これらすべてが唯一共有しているもの、つまりピン留めされたバージョンをダウンロードして展開するスイッチャー自身のロジックのためです。

## 修正と、それでも消えなかったシャドウ

エージェントが壊れた状態を、事前の説明なしに環境変数によるワークアラウンドで回避しようと試み始めたとき、私はそれを止めて理由を尋ねました。その問いかけが修正の方向を変えました。壊れたpnpmを回避するのではなく、pnpmを正しく再インストールするようエージェントに求め、公式インストーラーを直接実行することを承認しました。

```powershell
iwr https://get.pnpm.io/install.ps1 -useb | iex
```

これにより新しいシムと、クリーンな`PNPM_HOME`が作られました。更新された`PATH`を読み込んだPowerShellセッションでは、`pnpm --version`が`11.3.0`ときれいに表示され、そのシェルでは修正は機能しました。

続けて別のBashセッションで実行した`pnpm install`は、まったく何も再インストールされていないかのように、**まったく同じ**ENOENTエラーを、まったく同じ壊れたキャッシュパスを参照して出しました。この時点での反射的な反応は、再インストールがうまくいっていなかったのではないか、あるいはキャッシュが2度目に自壊したのではないかと疑うことです。どちらも違いました。

```bash
which pnpm
# /c/ProgramData/chocolatey/bin/pnpm
```

Bashが実行していたのは、たった今修復したばかりのpnpmではありませんでした。**Chocolatey経由でインストールされた、まったく別の古いインストール**を実行しており、それはBashの`PATH`上で新しいものより先に位置していて、なおかつ同じ壊れたスイッチャーの挙動にさらされ続けるほど古いものでした。**この証拠は、実は最初の診断の時点で一度すでに現れていました**。最初のエラーと並んで実行した`which pnpm`のチェックの中にです。当時はそれと気づかれないまま読み飛ばされました。うるさいENOENTのブロックが反応すべき明白な対象であり、その隣にある地味な1本のファイルパスはそうではなかったからです。

*Figure — PathResolution: どちらのシェルもPATH上の同じ2つのフォルダを検索していました。違っていたのは順序だけで、その順序がどちらのpnpmバイナリを実際に実行するかを決めていました。*

**原因が見えてしまえば、修正はたった1行でした**。そのシェルの`PATH`で、正しいインストールの`bin`フォルダをChocolateyのものより前に置くことです。

```bash
export PATH="/c/Users/<user>/AppData/Local/pnpm/bin:$PATH"
which pnpm
# /c/Users/<user>/AppData/Local/pnpm/bin/pnpm
```

## 動作確認

正しいバイナリが解決されるようになると、`pnpm install`が完了してこのマシンで初めて`node_modules`が生成され、`pnpm thumbnails:fix`も開始前に失敗することなく正規化スクリプトを実行しました。**両方のシェルで`pnpm --version`は今や`11.3.0`で一致しており、毎回同じインストールから返ってきます。**

## もう1つの無関係なWindowsの失敗：ビルドが自身の依存関係のシンボリックリンク作成をブロックされる

後日、同じマシン上で、別の記事を公開している最中に、これとは無関係な失敗が表面化しました。`pnpm build`のVercelアダプターが、サーバーバンドル自体はすでに正常にビルドし終えた後の途中でクラッシュしたのです。

```
EPERM: operation not permitted, symlink '.pnpm\sharp@0.35.3_@types+node@24.13.3\node_modules\sharp' -> 'C:\repositories\personal\oharu-tech-blog\.vercel\output\functions\_render.func\node_modules\sharp'
```

このアダプターは、サーバーレス関数の依存関係をpnpmのコンテンツアドレス方式のストアからシンボリックリンクすることでバンドルしており、シンボリックリンクの作成はまさに、Windowsが標準アカウントに対してデフォルトでブロックしている操作です。**修正はDeveloper Modeであって、昇格されたシェルではありません。**設定 → プライバシーとセキュリティ → 開発者向け → 開発者モードは、非管理者セッションには本来ない`SeCreateSymbolicLinkPrivilege`を付与し、そこでオンにするだけで十分でした（管理者として何かを実行する必要はありませんでした）。それが設定するレジストリキー`HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\AppModelUnlock\AllowDevelopmentWithoutDevLicense`は、シンボリックリンクの失敗にフル昇格セッションが必要だと決めつける前に確認する価値があります。存在しないか`0`であれば開発者モードはオフであることを意味し、それだけでこのビルドを再現できました。

同じビルドは、クラッシュの直前に、混同しやすいもう1つの警告を出していました。

```
The local Node.js version (26) is not supported by Vercel Serverless Functions.
Your project will use Node.js 24 as the runtime instead.
Consider switching your local version to 24.
```

Node 26は、このマシンの*Current*リリースラインであって、Vercelの関数が実際に動くLTSラインではありません。**この警告はビルドを止めませんでしたが、その直後のEPERMエラーは止めました**。そのため、両者を無関係な別々の失敗ではなく1つの失敗として読んでしまいやすくなっていました。Nodeのミスマッチは、AstroのVercelアダプターがダウングレードして回避するランタイムサポートの警告であり、シンボリックリンクのエラーは、どのNodeバージョンが動いているかとは無関係なWindowsの権限の問題です。

## その修正は定着しなかった

**シャドウ化したインストールを直したあの`export`は、それを実行したシェルだけを直し、それ以外には及びませんでした。** シェルスコープの環境変数は新しいターミナルには引き継がれないため、翌日、新しいセッションで同一のエラーが再び発生しました。

```
ERROR  Failed to switch pnpm to v11.3.0. Looks like pnpm CLI is missing at "C:\Users\<user>\AppData\Local\pnpm\.tools\@pnpm+win-x64\11.3.0\bin" or is incorrect
spawnSync C:\Users\<user>\AppData\Local\pnpm\.tools\@pnpm+win-x64\11.3.0\bin\pnpm ENOENT
```

エージェントは、これが新たな途中失敗のダウンロードなのか、それとも同じ決定論的な失敗なのかを確認するため、以前の診断を最初からやり直しました。壊れた`.tools\@pnpm+win-x64\11.3.0`フォルダを削除して再試行したのです。結果は同じ失敗でした。上書き設定を持たないシェルでの`where pnpm`は、再びChocolateyのバイナリを先頭に挙げ、AppDataのインストールをフルパスで直接実行すると`11.3.0`へきれいに切り替わりました。**動作するインストールは一度も壊れていませんでした。順位を下げられていただけであり、以前の修正はその順位を1つのシェルについてしか変えていませんでした。**

今回の修正は、競合するインストール自体を対象にしました。`choco uninstall pnpm -y`です。これも最初の試行は失敗し、`UnauthorizedAccessException: Access to the path 'C:\ProgramData\chocolatey\bin\pnpm.exe' is denied`というエラーが出ました。Chocolateyのファイルは`C:\ProgramData`配下にあり、削除するにはエージェントが自分では開けない昇格されたシェルが必要です。私自身が、管理者として開いたPowerShellからアンインストールを実行しました。Chocolateyのパッケージがなくなったことで、`where pnpm`はどのシェルでも常に1つのパスだけを返すようになり、持ち越すべき`export`もなくなりました。

## まとめ

**「古いChocolateyインストールが原因だった」という見立ては誤りであり、それを声に出して間違いだったと認める価値があります。** 実際に壊れていたのは、pnpm自身のセルフマネージドなバージョンスイッチャーが、ピン留めされたバージョンのインストールを完了できずにいたことで、これは同一のエラー文言で、互いに無関係な環境をまたいで報告されているバグです。修正が1回で済まず2回かかった原因は、あるシェルの`PATH`でより先に座っていた、もう1つの古いpnpmであり、その証拠はすでに最初の診断コマンドの時点で表面化していながら読まれていませんでした。pnpmを再インストールしたことでスイッチャーは修正されました。シェルが報告するバージョン番号を信用する前に、まず`which pnpm`を確認していれば、シャドウ化したインストールを2回目ではなく1回目で見つけられていたはずです。**`PATH`の並べ替えは、それを実行したシェルの範囲に閉じているため、恒久的な修正には最初からなり得ませんでした。**恒久的な修正だったのは、昇格されたシェルでシャドウ化していたインストールをまるごと削除することでした。

## 参考リンク

- [pnpm#9183: Failed to switch pnpm to vXX, ENOENT after a partial self-install](https://github.com/pnpm/pnpm/issues/9183)
- [pnpm#9046: Failed to switch pnpm to vv10.2.0](https://github.com/pnpm/pnpm/issues/9046)
- [pnpm#9192: Looks like pnpm CLI is missing at \\pnpm\\.tools\\](https://github.com/pnpm/pnpm/issues/9192)
- [pnpm#9715: Automatic download of pnpm version breaks when a home directory is a symlink](https://github.com/pnpm/pnpm/issues/9715)
