結論

npm ERR! code EACCES は、OS が書き込みや読み取りを拒んだという意味です。npm 自身の判断ではなく、システムコールが返した値がそのままコードになっています。

重要なのは、npm がこのエラーに対して2種類の文面を用意している点です。実装では、失敗した経路または書き込み先がキャッシュの置き場から始まっていて、かつ Windows でない場合にだけ、キャッシュの所有者を直す案内を出します。それ以外は、OS に拒まれたという汎用の文面になります。

つまり文面を読めば、直す対象が二分できます。キャッシュの所有者の話なのか、書き込み先そのものの権限の話なのか、という分かれ方です。

sudo を付けて回避するのは勧められません。多くの場合、それが次回以降の失敗の原因を作ります。root で作られたファイルがキャッシュに残り、通常の利用者では触れなくなるためです。

エラーが発生する処理段階

npm の処理は大きく3段階に分かれます。どの段階で拒まれたかで、疑う場所が変わります。

第一段階はキャッシュへの読み書きです。取得したパッケージの内容はキャッシュの置き場に保存されます。既定の場所は、POSIX 系が ~/.npm、Windows が %LocalAppData%\npm-cache です。

第二段階は導入先への展開です。通常の導入なら作業ディレクトリnode_modules、全体向けの導入なら prefix の下です。公式の説明によれば、全体向けの導入ではパッケージが {prefix}/lib/node_modules に置かれ、実行ファイルが {prefix}/bin に、説明書が {prefix}/share/man にそれぞれ結び付けられます。

第三段階は導入後のスクリプト実行です。ここで失敗する場合、拒まれているのは npm ではなくスクリプトが触ろうとした場所です。

npm ERR! pathnpm ERR! syscall の2行が、どの段階かを教えてくれます。

最初に確認すること

まず、拒まれた経路と操作を出力から読み取ります。

npm ERR! code EACCES
npm ERR! syscall mkdir
npm ERR! path /usr/local/lib/node_modules/typescript
npm ERR! errno -13
npm ERR! Error: EACCES: permission denied, mkdir '/usr/local/lib/node_modules/typescript'

path がどこを指しているかで、次に見る場所が決まります。キャッシュの置き場の下なら原因1、prefix の下なら原因2、作業ディレクトリの下なら原因3です。

その3つの場所を、実際の値で確認します。

npm config get cache
npm config get prefix
pwd

次に、その場所の所有者と権限を見ます。

ls -ld "$(npm config get cache)" "$(npm config get prefix)/lib/node_modules"

所有者が root になっていて、自分が root でないなら、そこが原因です。所有者は自分でも権限の欄に書き込みが無い場合は、権限の側の問題になります。

原因別の確認方法と解決策

原因1:キャッシュ配下が root 所有になっている

過去に sudo npm を実行したことがあると起きます。root で作られたファイルがキャッシュに残り、以後の通常実行が拒まれます。

この場合、npm は専用の文面を出します。実行中の利用者番号を埋め込んだ復旧コマンドまで示されます。

npm ERR! Your cache folder contains root-owned files, due to a bug in previous versions of npm which has since been addressed.
npm ERR!
npm ERR! To permanently fix this problem, please run:
npm ERR!   sudo chown -R 1000:1000 "/home/user/.npm"

確認方法は、root 所有のファイルが残っているかどうかです。

find "$(npm config get cache)" ! -user "$(id -un)" -print -quit

1行でも出力されれば該当します。対処は所有者の付け替えです。npm が示したコマンドをそのまま使えます。

sudo chown -R "$(id -u):$(id -g)" "$(npm config get cache)"

対象がキャッシュの置き場に限られていることを確認してから実行してください。npm config get cache の値が空だったり想定と違ったりする状態で流すと、範囲が広がります。

原因2:全体向けの導入先に書き込めない

npm install -g で起きる最も多い形です。prefix の既定値は、公式の説明によれば node の実行ファイルが置かれているディレクトリです。多くの環境では /usr/local になり、通常の利用者には書き込めません。

確認方法は導入先の権限です。

ls -ld "$(npm config get prefix)/lib/node_modules"

対処は2通りあります。安全なのは、書き込める場所を prefix に指定する方法です。

mkdir -p "$HOME/.npm-global"
npm config set prefix "$HOME/.npm-global"

このあと $HOME/.npm-global/bin を実行経路に加えてください。加えないと、導入したコマンドが見つかりません。

export PATH="$HOME/.npm-global/bin:$PATH"

もう1つは、node 自体を利用者の領域に入れ直す方法です。版を切り替える道具を使えば、node と npm が最初から利用者の所有になるため、このエラーは起きなくなります。

sudo npm install -g は避けてください。導入は成功しますが、キャッシュに root 所有のファイルが残り、原因1を作ります。

原因3:作業ディレクトリの所有者が実行利用者と違う

コンテナや CI で起きます。ホスト側のディレクトリコンテナに持ち込むと、番号だけが引き継がれます。ホスト側の所有者番号とコンテナ内の実行利用者の番号が違えば、書き込めません。

確認方法は、両側の番号の突き合わせです。

id -u
ls -ldn node_modules

ls -ldn は番号のまま表示するので、名前が解決できない環境でも比較できます。値が一致していなければ、これが原因です。

対処は、実行する利用者の番号をホスト側に合わせることです。

services:
  app:
    image: node:22
    user: "1000:1000"

所有者を変える方法もありますが、持ち込み元のホスト側にも影響します。どちらを変えてよいかを確認してから選んでください。

原因4:導入後のスクリプトが別の場所へ書こうとしている

pathキャッシュでも prefix でも作業ディレクトリでもない場合です。パッケージの導入後スクリプトが、システムの領域や他の利用者の領域に書こうとしています。

確認方法は、スクリプトを止めて切り分けることです。

npm install --ignore-scripts

これで通るなら、失敗しているのは導入そのものではなくスクリプトです。対処は、そのパッケージが何をしようとしているかを確認したうえで判断することになります。書き込み先を設定で変えられる場合が多くあります。

--ignore-scripts を恒久的な設定にすると、正常に必要なスクリプトも動かなくなります。切り分けの手段として使ってください。

近いエラーとの境界

EPERM は、実装上 EACCES と同じ分岐で処理されます。文面もほぼ同じで、Windows の場合だけ2行目が変わり、ファイルが編集用の道具やウイルス対策の常駐によって使用中である可能性に触れます。Windows で出ている場合は、権限ではなくファイルの使用中を先に疑ってください。

Windows では、キャッシュ配下であっても所有者を直す案内は出ません。実装の条件に Windows の除外が入っているためです。

EROFS は書き込み先が読み取り専用の場合、ENOSPC は容量が足りない場合です。いずれも権限とは別で、所有者を変えても解消しません。

E401 は、パッケージの置き場に対する認証の失敗です。ファイルの権限ではなく、通信相手に対する認証の話になります。

内部動作または公式仕様

npm の文面を組み立てる処理は、EACCESEPERM を同じ分岐で扱います。分岐の中で最初に判定するのが、失敗した経路がキャッシュ配下かどうかです。エラーオブジェクトpathdest のいずれかが、設定されているキャッシュの値で始まっているかを見ます。

その条件に加えて、実行中の環境が Windows でないことが求められます。両方を満たしたときだけ、キャッシュに root 所有のファイルが残っているという説明と、所有者を直すコマンドが出ます。コマンドに入る番号は、実行中の処理から取得した利用者番号と集団番号です。

条件を満たさない場合は汎用の文面になります。OS に拒まれたという1文と、現在の利用者ではこのファイルに触れられない可能性が高いという説明、そしてファイルとその上位ディレクトリ権限を確認するか、管理者として実行し直すようにという案内です。

設定の既定値も押さえておきます。キャッシュの置き場は、公式の説明によれば Windows が %LocalAppData%\npm-cache、POSIX 系が ~/.npm です。prefix は、全体向けの動作では node の実行ファイルが置かれているディレクトリが既定になります。

バージョン差・注意点

古い手順で見かける --unsafe-perm は、現在の npm には存在しません。npm 6 の設定定義には確かに含まれており、Windows か cygwin か、あるいは実行者が root でない場合に真になる作りでした。しかし npm 7.0.0 の設定定義には既に含まれておらず、8 系にも最新版にもありません。sudo npm install -g --unsafe-perm という指示を見かけても、現在の npm では設定として認識されません。

キャッシュの文面にある「以前の版の不具合」という表現にも注意が必要です。不具合そのものは修正済みですが、当時作られた root 所有のファイルは自動では消えません。そのため、修正後の npm を使っていても、過去に作られたファイルが残っていれば同じ文面が出ます。npm を更新しても解消しないのはこのためです。

sudo での回避は、その場は通っても後で効いてきます。root で導入した結果としてキャッシュに root 所有のファイルが増え、次に通常の利用者で実行したときに原因1の状態になります。

Editor’s Note

--unsafe-perm の廃止は、このエラーの対処法を調べるときに引っかかる点です。npm 7.0.0 は2020年10月12日に公開されており、この版の設定定義には既に unsafe-perm が含まれていません。

当時の状態としては、npm 6 系が広く使われており、root で実行したときにスクリプトの実行権限を落とす挙動があったため、それを無効にする設定として --unsafe-perm が使われていました。実装を見ると、Windows か cygwin の場合、利用者番号を扱う機能が使えない場合、あるいは実行者が root でない場合に既定で真になる形でした。裏返せば、root で実行したときだけ既定で偽になり、そこで問題が起きていたわけです。

現在も適用できるかという点では、適用できません。npm 6 は既に保守が終わっており、7 以降の設定定義にこの項目はありません。したがって、いま --unsafe-perm を含む手順を見つけた場合、それは npm 6 以前を前提にした情報です。同じ記事に書かれている他の対処も、古い前提のままである可能性を疑ってください。

現在の推奨は、権限を緩めるのではなく、書き込める場所を使うことです。prefix を利用者の領域に移すか、node 自体を利用者の所有で入れ直すかのどちらかになります。

参考資料


免責事項:本記事の内容は、執筆時点の公開情報をもとに作成したものです。ソフトウェアの仕様は予告なく変更されることがあります。最新の情報は各ツールの公式サポートページをご確認ください。本記事の情報を利用した結果生じたいかなる損害についても、著者および運営者は責任を負いかねます。