WSLにnpmのグローバルパッケージを入れるとき、なぜホームディレクトリが推奨なのかMEMO
きっかけ
WSL上のLaravelプロジェクトを触るためにClaude Codeを入れようとして、いきなり転んだ。
npm install -g @anthropic-ai/claude-code
npm error code EACCES
npm error syscall mkdir
npm error path /usr/lib/node_modules/@anthropic-ai
npm error Error: EACCES: permission denied, mkdir '/usr/lib/node_modules/@anthropic-ai'
権限がないと言われている。エラーメッセージ自体は親切で「try running the command again as root/Administrator」と書いてあるので、素直にsudoを付けたくなる。だがそこで止まって考えたほうがいい。
npmのグローバル領域はどこにあるのか
npm install -g の-gはグローバルインストールの意味だが、この「グローバル」がどこを指すかはprefixという設定値で決まる。確認はこう。
npm config get prefix
Ubuntu標準のnodeを使っている場合、たいてい/usrが返ってくる。実際のパッケージは/usr/lib/node_modules配下に置かれ、実行ファイルへのシンボリックリンクが/usr/binに張られる。
/usrはOS管理下の領域なので、一般ユーザーには書き込み権限がない。だからmkdirで蹴られた。原因としてはそれだけの話で、npmが壊れているわけでもnodeのバージョンが悪いわけでもない。
sudoで解決してはいけないのか
動きはする。ただし後で困る種類の「動く」だ。
sudo npm install -g で入れたパッケージはroot所有になる。すると以降そのパッケージを更新するにも削除するにもsudoが要る。ここまでは我慢できる。
問題は混ざったときだ。あるパッケージはroot所有、別のパッケージは一般ユーザー所有、という状態が生まれると、npmが依存関係を解決する際にキャッシュディレクトリの所有者が食い違って謎のEACCESが再発する。~/.npmキャッシュに一度でもrootが書き込むと、その後の通常インストールが軒並み転ぶ、という事故は昔からよくある。
さらにグローバルインストールはスクリプト実行を伴うことがある。postinstallで任意のコマンドが走る仕組みなので、それをroot権限で走らせるということは、npmレジストリから落ちてきたコードにroot権限を渡すということでもある。悪意がなくても、パッケージのインストールスクリプトのバグでシステム領域が壊れる可能性は残る。
要するに、システム全体を巻き込むリスクを、個人用ツールを一本入れるために背負う必要がない。
ホームディレクトリに置く
prefixを自分の家に向け直す。これで-gの意味が「システム全体」から「このユーザー全体」に変わる。
mkdir -p ~/.npm-global
npm config set prefix ~/.npm-global
echo 'export PATH=~/.npm-global/bin:$PATH' >> ~/.bashrc
source ~/.bashrc
npm install -g @anthropic-ai/claude-code
PATHを通しているのは、~/.npm-global/binに置かれた実行ファイルをどのディレクトリからでも叩けるようにするため。これを忘れるとインストールは成功しているのにcommand not foundになり、「入ってないのか?」と混乱する。詰まったらまずここを疑うといい。
設定が効いているかの確認。
npm config get prefix
# => /home/yuzuru/.npm-global
npm list -g --depth=0
よくある勘違い
prefixを変えるとnpm list -gに何も出なくなるのではないか、という不安を持つ人がいる。これは杞憂で、npm list -gもprefixを参照して探しに行くので、移した先の中身がちゃんと表示される。むしろsudo版と通常版が混ざっているときのほうが「さっき入れたはずのものが見当たらない」という状態になりやすい。
もう一つ、「ライブラリは/usr/libにどんどん積まれていくのでは」という疑問。これはaptとnpmを混同している。aptで入れたものは従来どおり/usr/lib配下に入る。prefixの変更が影響するのはnpmのグローバルパッケージだけで、OSのパッケージ管理には一切関係しない。
置き場所は三層ある
混乱の元は、Linuxのソフト置き場が一箇所ではないことにある。整理するとこうなる。
| 置き場 | 対象範囲 | 必要な権限 | Windowsでの類比 |
|---|---|---|---|
/usr/lib |
全ユーザー | root | C:\Program Files |
~/.npm-global |
自分だけ | 不要 | C:\Users\<name>\AppData\Local\Programs |
./node_modules |
そのプロジェクトだけ | 不要 | アプリに同梱されたDLL |
縦の入れ子ではなく、横に並んだ三つの棚だと捉えるとわかりやすい。分類の軸は「誰のものか」であって「何を入れるか」ではない。
Claude Codeのような個人用の開発ツールは二段目が適切。全ユーザーに配る必要もなければ、プロジェクトに同梱する必要もないからだ。
権限が要るレベルの見分け方
パスを見れば判断できる。
| パス | 権限 | 意味 |
|---|---|---|
/usr, /etc, /opt, /var
|
root | OSと全ユーザーが共有する領域 |
/home/<user> 配下 |
本人のみ | 各ユーザーの専有領域 |
/tmp |
全員書き込み可 | 一時領域。再起動で消える前提 |
sudoを打つ前に「これは全員に影響する変更か?」と一度考える習慣をつけると、事故が減る。全員に影響しないなら、たいていホーム配下で済む。
余談
WSLでもう一つ気をつけたいのが作業ディレクトリの位置。/mnt/c/...配下、つまりWindows側のファイルシステムでnpmを走らせると劇的に遅い。WSL2はext4とNTFSの間を9Pプロトコル越しにやり取りするため、node_modulesのような小さいファイルが数万個生まれる処理と壊滅的に相性が悪い。プロジェクトは/home/<user>や/var/wwwなどLinux側に置くのが鉄則になる。
ついでに言うと、この9P越しという構造はWindows側からWSLのファイルを見るときにも顔を出す。エクスプローラで見える\\wsl.localhost\<distro>はUNCパス、つまりネットワーク共有として扱われるので、アプリケーションによっては「ネットワーク上の場所」と判定されて拒否される。ネットワークドライブに割り当てても、解決した先がUNCなので同じこと。WSL内で完結するツールを使うのが結局は早い、という結論に落ち着きやすい。
nodeのバージョン管理にnvmやvoltaを使っている場合は、そもそもnode本体がホーム配下に入るので、この権限問題は最初から発生しない。今回のようにディストリのaptで入れたnodeを使っているケース特有の話だと言える。長く付き合うならバージョン管理ツールの導入を検討してもいい。
まとめ
-
npm install -gのEACCESは、prefixがOS管理領域の/usrを指しているために起きる -
sudoで回避するとroot所有と一般ユーザー所有が混在し、後々のEACCESや権限事故の温床になる - グローバルインストールは
postinstallで任意コードが走りうるため、root権限を渡すこと自体が望ましくない - prefixを
~/.npm-globalに向ければ、権限不要かつ影響範囲が自分だけに閉じる -
PATHの追記を忘れるとcommand not foundになる。インストール失敗と誤認しやすい罠 - prefix変更後も
npm list -gは移した先を見に行くので、一覧から消える心配はない - aptとnpmは別系統。prefixの変更が
/usr/libのOSパッケージに影響することはない - 置き場は「全ユーザー」「自分だけ」「プロジェクトだけ」の三層。軸は誰のものかであって何を入れるかではない
- 個人用の開発ツールは「自分だけ」の層が適切
-
sudoを打つ前に「全員に影響する変更か」を自問すると事故が減る - WSLでは
/mnt/c配下での作業が極端に遅い。プロジェクトはLinux側に置く