npm tried to change ownership of a file or directory but the OS/FS blocks lchown (common on Windows/WSL and non-root global prefixes). Use user-owned prefixes and avoid chown on unsupported filesystems.
Fixes npm ERR! Error: EPERM: operation not permitted, lchown '/usr/local/lib/node_modules'
npm config get prefix
stat -f -c %T $(npm config get prefix) 2>/dev/null || stat -f $(npm config get prefix)npm config get prefixstat -f -c %T $(npm config get prefix) 2>/dev/null || stat -f $(npm config get prefix)npm ERR! Error: EPERM: operation not permitted, lchown '/usr/local/lib/node_modules'
npm occasionally calls lchown when installing globally or adjusting permissions in node_modules. On Windows/WSL and many container mounts, lchown is either unsupported or restricted, so the syscall returns EPERM and the install stops. This happens when installing globally as a non-root user, when the prefix is root-owned, inside Docker/CI with mismatched users, or on NTFS/SMB where chown semantics are limited. The fix is usually to avoid needing chown by keeping npm files in user-owned locations.
npm config get prefix
stat -f -c %T $(npm config get prefix) 2>/dev/null || stat -f $(npm config get prefix)mkdir -p ~/.npm-global
npm config set prefix ~/.npm-global
npm config set cache ~/.npm-cache
export PATH="$HOME/.npm-global/bin:$PATH"On NTFS/SMB mounts, chown is ignored or blocked. Install on a Linux/ext4 path (WSL ~/projects) or run without forcing ownership changes.
sudo rm -rf $(npm config get prefix)/lib/node_modules/.staging 2>/dev/null || true
npm cache clean --force
npm install -g <package>Run containers with matching UID/GID or chown the workspace/prefix once at startup so npm never needs to call lchown.
Homebrew installs under /opt/homebrew on macOS can drift to root-owned; prefer nvm/fnm to keep globals in $HOME and avoid chown. In CI, ensure the workspace is owned by the job user before npm ci; copying caches from root-owned layers often triggers lchown failures. On Windows/WSL, chown is largely a no-op—using per-user prefixes and avoiding sudo eliminates the syscall entirely.