Claude Media
Claude Codeのsandbox内でpnpmが固まる原因と回避 — バージョン固定が引き金

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条件が重なったときに固まります。

  1. macOSでBashのsandboxが有効
  2. package.jsonが、手元のpnpmと違うバージョンを固定している
  3. 固定の書き方が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. 1

    pnpmがバージョンの切り替えを始める

    固定されたバージョンへ切り替えるため、pnpm 12は~/Library/pnpm/package-manager-store/v11/tmp/engine-locks/pnpm@<バージョン>.lockというディレクトリを作ってロックを取ります。

  2. 2

    sandboxが書き込みを拒む

    ~/Library/pnpmは、sandboxの既定の書き込み許可に入っていません。mkdirはEPERMで失敗します。

  3. 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 installallowWriteで許可通る環境変数で切り替えを止める別途、置き場への書き込みが必要
sandboxの書き込み範囲allowWriteで許可広がる環境変数で切り替えを止める変わらない
向く場面allowWriteで許可バージョンを揃えて作業したい環境変数で切り替えを止める--versionや読み取りだけで済む

固まったときの切り分け

固まったコマンドが本当にこの症状かを、順に確かめます。

切り分け

pnpmが固まったときの切り分け

  1. Qsandboxの外で同じ操作をすると、すぐ終わりますか?
    答えがいいえのとき

    sandboxの外でも固まるなら、sandboxとは別の原因です。pnpmやネットワーク側を確認します。

    答えがはい
  2. Qpackage.jsonがpnpmのバージョンを固定していて、インストール済みと違いますか?
    答えがいいえのとき

    packageManagerかdevEngines.packageManagerのバージョンを見ます。固定が無いか、バージョンが一致しているなら、この記事の症状ではありません。

    答えがはい
  3. 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-dir

Operation 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が失敗する原因のように、拒否される層ごとに見え方が違います。

この記事を共有:XはてブLinkedIn