Claude Codeのsandbox内でpnpmが固まる原因と回避 — バージョン固定が引き金
package.jsonでpnpmのバージョンを固定したプロジェクトで、sandbox内のpnpmが--versionまで固まる症状の原因と、allowWriteと環境変数の2つの回避、切り分けの手順をまとめます。
macOSでBashのsandboxを有効にしたClaude Codeにpnpmを実行させると、pnpm --versionすら返ってこないことがあります。エラーも出ず、Bashのタイムアウトまでただ待たされます。
引き金は、package.jsonがインストール済みと違うバージョンのpnpmを固定していることです。原因はpnpmが取ろうとするロックのディレクトリを、sandboxが作らせないことにあります。回避は2つあり、~/Library/pnpmを書き込み許可に加えるか、バージョンの切り替え自体を止めるかです。
この症状はGitHubのIssue #98938で報告されています。報告の環境はmacOS、Claude Code 2.1.278(デスクトップアプリのCodeタブ)、pnpm 12.5.1で、sandboxは既定の設定でした。
どんなときに固まるのか
次の3条件が重なったときに固まります。
- macOSでBashのsandboxが有効
package.jsonが、手元のpnpmと違うバージョンを固定している- 固定の書き方が
packageManagerか、devEngines.packageManagerでonFailがdownload
固定の書き方は、たとえば次のとおりです。
{
"name": "repro",
"devEngines": {
"packageManager": {
"name": "pnpm",
"version": "12.8.0",
"onFail": "download"
}
}
}インストール済みが12.5.1のとき、このディレクトリでpnpm --versionを走らせると固まります。報告者が試した組み合わせは次の表のとおりです。
| 条件 | 結果 |
|---|---|
| 固定とインストール済みのバージョンが同じ(12.5.1) | 結果12.5.1がすぐ返る |
12.8.0を固定、onFail: "error" | 結果ERR_PNPM_BAD_PM_VERSIONですぐ失敗する |
12.7.0・12.8.0・12.8.1を固定(pnpmのキャッシュに取得済み)、onFail: "download" | 結果固まる |
上と同じでallowed_domainsにregistry.npmjs.orgを追加 | 結果固まる |
| バージョンの切り替えが起きない状態でネットワークに出る | 結果動く |
4行目が重要です。ネットワークの許可を足しても直らないので、registryへ出られないせいではありません。キャッシュにあるバージョンを固定しても固まる点から、ダウンロードの失敗でもないことが分かります。
原因はロックディレクトリの作成拒否
報告者が突き止めた流れは3段です。
固まるまでの流れ
- 1
pnpmがバージョンの切り替えを始める
固定されたバージョンへ切り替えるため、pnpm 12は
~/Library/pnpm/package-manager-store/v11/tmp/engine-locks/pnpm@<バージョン>.lockというディレクトリを作ってロックを取ります。 - 2
sandboxが書き込みを拒む
~/Library/pnpmは、sandboxの既定の書き込み許可に入っていません。mkdirはEPERMで失敗します。 - 3
pnpmが待ち続ける
pnpmは
EPERMを失敗として扱わず、ロックが取れるのを待ち続けます。コマンドは終わらず、Bashのタイムアウトで打ち切られます。
sandboxの既定の書き込み先は、作業ディレクトリ、ユーザーごとの一時ディレクトリ、--add-dirなどで追加したディレクトリです。ホーム配下のパッケージマネージャーの置き場は含まれません。kubectl、terraform、npmのように外へ書く必要があるコマンドは、sandbox.filesystem.allowWriteで書き込み先を許可します。
報告者は、pnpmがEPERMで待ち続ける挙動そのものはpnpm側の問題だとして、別のpnpmの報告に切り分ける立場です。このIssueが扱うのは、Claude Code側の次の2点です。
- ファイルシステムへの書き込み拒否が、エージェントに知らされない
- 既定の書き込み許可に、パッケージマネージャーの置き場がない
なぜ原因が見えにくいのか
見えにくさの正体は、拒否が無言であることです。報告によると、ネットワークの拒否は<sandbox_violations>ブロックに出ますが、ファイルへの書き込み拒否は何も出ません。コマンドが固まっているだけに見えるので、sandboxが原因だと気づく手がかりがありません。
報告者は原因の特定に、バージョンの比較、pnpmのバイナリ内の文字列の確認、mkdirの直接実行を手作業で重ねたと書いています。
sandboxの外では同じコマンドが問題なく動くことも、判断を遅らせます。ネットワークの拒否はホスト名が結果に出るので切り分けやすい一方、書き込みの拒否にはその手がかりがありません。sandboxの動作確認の例でも、touch ~/sandbox-probeはOperation not permittedで失敗すると説明されています。書き込みの拒否は、普通のコマンドならEPERMとして表に出ます。今回は呼び出し側のpnpmがそれを握りつぶすため、固まるという形で現れます。
別の層の失敗が似た見え方をする例は、Claude CodeのGit Bashで出力が空になる症状にもあります。pnpmが固まるときは、「出力がない」ではなく「戻ってこない」という違いで見分けられます。
回避1:~/Library/pnpmを書き込み許可に加える
sandboxを保ったまま直すなら、sandbox.filesystem.allowWriteにpnpmの置き場を足します。設定ファイルは~/.claude/settings.jsonなどの設定スコープに書きます。
{
"sandbox": {
"enabled": true,
"filesystem": {
"allowWrite": ["~/Library/pnpm"]
}
}
}sandboxのパスは、~がホーム配下、/tmp/buildが絶対パスという慣例で書きます。Read・Edit権限ルールの//path・/pathとは書式が違います。同じ配列を複数のスコープに書いたときは、置き換えではなく結合されます。
セッション中に書き換えた場合も、次のsandbox内コマンドから新しいパスで動きます。再起動は不要です。設定キーの全体像はallowWriteとdenyWriteの解説にあります。
この回避には、実務上の利点があります。pnpm installは、バージョンが一致していてもpnpmの置き場への書き込みが要ります。報告者も、sandboxの中で使える逃げ道はdangerouslyDisableSandboxだけで、auto modeはそれを正しく拒否するため、エージェントがpnpmを実行できなくなると書いています。allowWriteを足すのは、installまで通すための手順です。
許可の範囲が広がることは、先に承知しておく必要があります。~/Library/pnpmに入っているのは、pnpm自身のバイナリとストア、ロック用の一時ファイルです。sandbox内のコマンドが、そこへ書けるようになります。
回避2:バージョンの切り替えを止める環境変数
書き込み許可を増やしたくない場合は、環境変数PNPM_CONFIG_MANAGE_PACKAGE_MANAGER_VERSIONS=falseで、固定されたバージョンへの切り替えを止められます。報告者の環境では、この変数を付けるとpnpm --versionが動きました。
PNPM_CONFIG_MANAGE_PACKAGE_MANAGER_VERSIONS=false pnpm --version注意点は、動くpnpmのバージョンです。切り替えを飛ばすので、動くのは固定されたバージョンではなく、インストール済みのバージョン(報告では12.5.1)になります。package.jsonが想定するバージョンと手元のバージョンがずれたまま作業することになるため、バージョン差で挙動が変わるプロジェクトでは使いにくい回避です。
使い分けの目安は次の表です。
| 観点 | allowWriteで許可 | 環境変数で切り替えを止める |
|---|---|---|
| 動くpnpmのバージョン | allowWriteで許可固定どおり | 環境変数で切り替えを止めるインストール済みのもの |
pnpm install | allowWriteで許可通る | 環境変数で切り替えを止める別途、置き場への書き込みが必要 |
| sandboxの書き込み範囲 | allowWriteで許可広がる | 環境変数で切り替えを止める変わらない |
| 向く場面 | allowWriteで許可バージョンを揃えて作業したい | 環境変数で切り替えを止める--versionや読み取りだけで済む |
固まったときの切り分け
固まったコマンドが本当にこの症状かを、順に確かめます。
pnpmが固まったときの切り分け
- Qsandboxの外で同じ操作をすると、すぐ終わりますか?答えがいいえのとき答えがはい
sandboxの外でも固まるなら、sandboxとは別の原因です。pnpmやネットワーク側を確認します。
- Qpackage.jsonがpnpmのバージョンを固定していて、インストール済みと違いますか?答えがいいえのとき答えがはい
packageManagerかdevEngines.packageManagerのバージョンを見ます。固定が無いか、バージョンが一致しているなら、この記事の症状ではありません。 - Qpnpmの置き場へのmkdirが、sandbox内で拒否されますか?答えがいいえのとき答えがはい
mkdirが通るなら、pnpmの置き場の書き込み拒否は原因ではありません。
allowWriteを足すか、切り替えを止める
EPERMなら、この記事の症状です。固定どおりのバージョンが要るかで、回避1か回避2を選びます。
3問目は、Claude Codeにsandbox内で書き込みを試させて確かめます。報告者はPythonからos.mkdirでロック用のディレクトリを直接作り、EPERMになることを確認しました。シェルならmkdirで同じことができます。
mkdir ~/Library/pnpm/probe-dir && rmdir ~/Library/pnpm/probe-dirOperation not permittedが出れば、pnpmの置き場が書き込み許可に入っていません。allowWriteを足したあとに同じコマンドが通れば、許可が効いています。
sandboxの外のターミナルで同じ操作をするときは、Claude Codeの!プロンプトに入力した行がsandbox外で動く点に注意が要ります。sandboxの検証は、Claude Codeにコマンドを実行させて行います。
試すときに踏みやすい点
onFail: "error"に変えると、固まらずにERR_PNPM_BAD_PM_VERSIONですぐ失敗します。原因の切り分けには使えますが、バージョンの不一致は解消しません。pnpmのリファレンスには、マニフェストを編集せずにonFailの挙動を上書くpmOnFail設定があります。ただし、この設定がsandbox内の固まりに効くかは、報告では試されていませんallowed_domainsを増やしても直りません。表のとおり、ネットワークは原因ではありません- 固定したバージョンがキャッシュにあっても固まります。「ダウンロード済みだから大丈夫」とはなりません
~/.local/share/pnpm、~/.npm、~/Library/Caches/pnpmも、報告者がパッケージマネージャーの置き場の候補として挙げています。固まりの原因として検証されたのは~/Library/pnpmの下のロックだけです
Linux・WSL2で同じ固まり方をするかは、報告にありません。報告のプラットフォームラベルはplatform:macosです。
まとめ
pnpmのバージョンを固定したプロジェクトでsandbox内のpnpmが固まるのは、ロック用ディレクトリの作成がEPERMで拒否され、pnpmがそれを待ち続けるためです。ネットワークではなく書き込み許可の問題なので、allowWriteに~/Library/pnpmを足すのが、バージョンを揃えたままinstallまで通す手段です。バージョンの切り替えを止める環境変数は、書き込み許可を増やしたくない場面の次善策になります。
報告はopenで、修正バージョンの記載はありません。ファイルへの書き込み拒否をエージェントに知らせる改善も、報告者の要望の段階です。別のsandbox内の失敗は、ネットワーク側ならsandboxから繋がらない原因、書き込み側ならgit addが失敗する原因のように、拒否される層ごとに見え方が違います。