メモログ

npm script warns permission errors in the cache directory

(そんなことするかどうかは置いておいて)たとえばdocker-node-npm-scripti-permission-issue-demoGithub codespaces上で起動して、npm ciして、npm run buildを実行すると、permission denied, scandir '/root/.npm/_logs' というエラーの WARN が発生する。

原因はnpm run buildの中身が npm run tsc になっているため。root ユーザーで別のユーザーが所有権を持つディレクトリで実行すると問題になる。

解決方法は Docker container を実行するユーザーをnodeに変更するとか、npm configで、npm config set cache /tmpと設定するとか(export npm_config_cache=/tmpでもいける雰囲気)、所有権を調整すると解決する。解決方法は他にもいろいろあると思う。

再現手順はdocker-node-npm-scripti-permission-issue-demoの codespaces の VS code の中でターミナルを開いて、以下のような感じで Docker container を起動して、npm cinpm run buildを実行するとエラーを再現することができる。動作環境は Node.js 16.18.0 で npm は 8.19.2。たぶん Node.js 18.x でも発生する(npm のバージョンは変わらないので)。

docker build -f Dockerfile -t demo .
docker run -it --rm -v `pwd`:/current -w /current demo bash
root@71524f7f2bea:/current# npm ci
... (snip) ...
added 1 package, and audited 2 packages in 2s
found 0 vulnerabilities
... (snip) ...
root@71524f7f2bea:/current# npm run build

> docker-node-npm-scripti-permission-issue-demo@1.0.0 build
> npm run tsc

npm WARN logfile Error: EACCES: permission denied, scandir '/root/.npm/_logs'
npm WARN logfile  error cleaning log files [Error: EACCES: permission denied, scandir '/root/.npm/_logs'] {
npm WARN logfile   errno: -13,
npm WARN logfile   code: 'EACCES',
npm WARN logfile   syscall: 'scandir',
npm WARN logfile   path: '/root/.npm/_logs'
npm WARN logfile }

> docker-node-npm-scripti-permission-issue-demo@1.0.0 tsc
> tsc

npm ERR! code EACCES
npm ERR! syscall mkdir
npm ERR! path /root/.npm/_cacache/tmp
npm ERR! errno -13
npm ERR!
npm ERR! Your cache folder contains root-owned files, due to a bug in
npm ERR! 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:0 "/root/.npm"

npm ERR! Log files were not written due to an error writing to the directory: /root/.npm/_logs
npm ERR! You can rerun the command with `--loglevel=verbose` to see the logs in your terminal

以下は詳細。

npm の script は@npmcli/run-scriptが実行スクリプトで/usr/local/lib/node_modules/npm/node_modules/@npmcliにある。これのバージョンは 4.2.1 なので、run-script.jsが実際の処理になる。これをrun-script-pkg.js@npmcli/promise-spawnと辿っていくと、promise-spawn/lib/index.jsに到達する。

ここで実行しているinfer-ownerは、スクリプトを実行するディレクトリの所有権を推測して、ディレクトリの uid、gid を返してくれる。run-script.js ではこれを利用してchild_process.spawnを uid, gid 付きで実行する。

codespaces のディレクトリは codespace(uid:1000)がオーナーで、Docker container のユーザーはデフォルトでは root(uid:0)なので、codespace が所有するディレクトリで root ユーザーが処理を実行する形になる。

具体的には root ユーザーが npm script が実行したとき、uid:1000, gid:0 でchild_process.spawnを実行する。ここまでは特に問題ない。

npm script の中でnpm runを記述していると、run-scriptの中でrun-scriptを実行する状態になる(npm script も npm run もrun-scriptで実行するので)。

つまり、root(uid:0)ユーザーが、uid:1000, gid:0 で実行した処理の中で、uid:1000, gid:0 で処理を実行する。するとnpm cacheのディレクトリ、/root/.npm に対してログを出力しようとしてパーミッションエラーとなる雰囲気。なぜ root ユーザーのキャッシュディレクトリを参照するとエラーになるかは理解できていない。run-script を実行してるユーザーの uid でファイルアクセスしようとするためなのだろうか。

なので、Docker container の実行ユーザーを root ではないユーザーに変更するか、root ユーザーを維持したければ、スクリプト実行前にnpm config set cache /tmpみたいな感じで、キャッシュディレクトリを適当な場所に変更しておけば、問題は発生しない。

私について

Yutaka Yamaguchi
東京在住。TypeScript, Node.js, Reactなどフロンエンドが主力。Perlも書く。SwiftやRubyも過去には使ってた。過去のTOEIC 860くらい。