Node.js Error

Error: EPERM: operation not permitted

error code: EPERM

Complete reference — all causes on Windows, macOS, Linux, and WSL, with every fix pattern.

Quick Answer: Node.js throws Error: EPERM: operation not permitted most often on Windows when npm install tries to rename or delete a file inside node_modules that another process (VS Code, antivirus, a stuck node.exe) holds open. Fix: (1) close VS Code; (2) kill all node.exe processes in Task Manager; (3) add node_modules to Windows Defender exclusions; (4) delete node_modules with npx rimraf node_modules; (5) run npm cache clean --force; (6) rerun npm install. For symlink errors, enable Windows Developer Mode.

What is EPERM?

EPERM stands for Error: operation not PERMitted — a POSIX system error (errno 1 on Linux/macOS, errno -4048 on Windows) returned by the OS kernel when a file system operation is refused at a level above ordinary file permission bits.

It is important to distinguish EPERM from its sibling EACCES (permission denied): EACCES means the process lacks read/write/execute permission on a specific file or directory (a chmod/chown problem). EPERM means the operation itself is not permitted in the current OS context, regardless of file permissions — for example, creating a symlink without the required Windows privilege, renaming a file another process has locked, or writing to a read-only filesystem.

Exact error messages you will see:
Error: EPERM: operation not permitted, rename 'C:\project\node_modules\.staging\...' -> 'C:\project\node_modules\...'
Error: EPERM: operation not permitted, unlink 'C:\project\node_modules\some-package\index.js'
Error: EPERM: operation not permitted, symlink '../some-bin' -> 'C:\project\node_modules\.bin\some-bin'
Error: EPERM: operation not permitted, mkdir 'C:\project\node_modules\.cache'
npm ERR! code EPERM
npm ERR! syscall rename
npm ERR! path C:\Users\user\AppData\Roaming\npm-cache\_cacache\tmp\...

Full Error Example

npm ERR! code EPERM
npm ERR! syscall rename
npm ERR! path C:\project\node_modules\.staging\lodash-9a4f2c1b\package.json
npm ERR! dest C:\project\node_modules\lodash\package.json
npm ERR! errno -4048
npm ERR! Error: EPERM: operation not permitted, rename
npm ERR!   'C:\project\node_modules\.staging\lodash-9a4f2c1b\package.json'
npm ERR!   -> 'C:\project\node_modules\lodash\package.json'
npm ERR!
npm ERR! The operation was rejected by your operating system.
npm ERR! It is likely you do not have the permissions to access this file as the current user
npm ERR! If you believe this might be a permissions problem, please double-check the
npm ERR! permissions of the file and its containing directories, or try running
npm ERR! the command again as root/Administrator.
    at Object.rename (node:fs:1281:3) {
  errno: -4048,
  code: 'EPERM',
  syscall: 'rename',
  path: 'C:\\project\\node_modules\\.staging\\lodash-9a4f2c1b\\package.json',
  dest: 'C:\\project\\node_modules\\lodash\\package.json'
}

Key diagnostic fields on the error object:

FieldValue in exampleMeaning
code'EPERM'Operation not permitted — OS-level refusal
syscall'rename'The fs operation that failed: rename, unlink, symlink, mkdir, open, rmdir
pathstaging pathThe source path — check which process owns this file
destfinal pathThe destination (present on rename errors)
errno-4048Windows error mapping; -1 on Linux/macOS

EPERM vs. EACCES — Know Which You Have

ErrorKernel meaningCommon Node.js scenarioPrimary fix
EPERM Operation not permitted in this context Symlink without privilege, rename of locked file, write to read-only FS Close conflicting process, enable Developer Mode, run as admin
EACCES Permission bits deny access chmod 000 file then read it; npm global install without sudo chmod +r / chown / fix npm prefix

On Windows, EPERM is used far more broadly than POSIX strictly defines it because the Windows kernel maps many error conditions (sharing violations, privilege checks) to a single EPERM code. If you see EPERM during npm install on Windows, a file handle conflict is the first thing to investigate, not file permission bits.

All Causes at a Glance

CauseSyscall in errorOS most affectedFix summary
IDE/file watcher holding a handle in node_modulesrename, unlinkWindowsClose VS Code; kill all node.exe processes
Antivirus scanning node_modules while npm writesrename, mkdirWindowsAdd node_modules and npm cache to exclusions
Stuck node.exe or npm.cmd process from prior runrename, rmdirWindowsTask Manager → end node.exe / npm.cmd
Symlink creation without SeCreateSymbolicLinkPrivilegesymlinkWindowsEnable Developer Mode or run as Administrator
Corrupted npm cacherenameAll (worse on Windows)npm cache clean --force
Read-only file or directoryopen, unlink, renameAllchmod +w (Linux/macOS) / attrib -r (Windows)
Project on NTFS mount in WSL (/mnt/c/...)symlink, renameWSLMove project to WSL native filesystem (~/projects/)
Mixing Windows Node.js and WSL Node.js on same pathvariesWSLUse Linux Node.js exclusively within WSL shell
npm cache/prefix on a network sharerenameWindows (corporate)Move npm cache to local path with npm config set cache
Long paths exceeding 260 charactersopen, mkdirWindowsEnable Win32 long paths; use rimraf
Writing to C:\Program Files or system pathsopen, mkdirWindowsMove project to user home directory

Cause 1 – IDE / File Watcher Holds an Open Handle

This is by far the most common cause on Windows. Visual Studio Code, WebStorm, and other IDEs use file system watchers to provide features like auto-import, IntelliSense, and live reload. These watchers hold open read handles on files inside node_modules. When npm install needs to rename a staging file into its final location — or delete an old version of a package — Windows refuses the operation with EPERM because the file is held by the IDE.

# Symptom — seen during npm install or npm ci
npm ERR! code EPERM
npm ERR! syscall rename
npm ERR! path C:\project\node_modules\.staging\react-abc123\index.js
npm ERR! dest C:\project\node_modules\react\index.js

Fix: close VS Code and kill node processes

# PowerShell — kill all Node.js and npm processes
Get-Process node, npm -ErrorAction SilentlyContinue | Stop-Process -Force

# Then verify no node processes remain
Get-Process node -ErrorAction SilentlyContinue

After killing the processes, wait 5–10 seconds for Windows to release all file handles, then rerun npm install. If the error still occurs, reboot — Windows releases all handles on reboot unconditionally.

Tip: In VS Code settings, add node_modules to files.watcherExclude so the editor never watches inside it:
// .vscode/settings.json
{
  "files.watcherExclude": {
    "**/node_modules/**": true
  },
  "files.exclude": {
    "**/node_modules": true
  }
}

Cause 2 – Antivirus Scanning node_modules

Windows Defender and third-party antivirus software (McAfee, Norton, Avast, Kaspersky) scan each file as it is written to disk. When npm install unpacks hundreds of small files rapidly, the antivirus scanner opens each file for reading at the exact moment npm tries to rename it from the staging directory. The rename fails with EPERM.

This cause produces intermittent EPERM failures — the error appears on some runs but not others, depending on which file the antivirus happened to be scanning at that moment.

Fix: add exclusions to Windows Defender

# PowerShell — add project node_modules to Defender exclusions
Add-MpPreference -ExclusionPath "C:\project\node_modules"

# Also exclude the npm cache directory
Add-MpPreference -ExclusionPath "$env:APPDATA\npm-cache"

# Or exclude by process: exclude node.exe and npm.cmd from scanning
Add-MpPreference -ExclusionProcess "node.exe"
Add-MpPreference -ExclusionProcess "npm.cmd"

Via GUI: Windows Security → Virus & threat protection → Manage settings → Exclusions → Add or remove exclusions → Add an exclusion → Folder → select your project root.

Security note: Excluding node_modules from antivirus scanning is standard practice in professional development environments — npm's own documentation recommends it. The packages installed via npm are from a registry with its own integrity checks (package-lock.json SHA hashes). However, never exclude paths you do not control.

Cause 3 – Stuck node.exe or npm.cmd Processes

A prior npm install that was Ctrl+C'd, a crashed dev server, or a runaway build script can leave a node.exe process running in the background with files in node_modules still open. The next npm install then fails because Windows will not rename or delete an open file.

# Command Prompt — find all node and npm processes
tasklist | findstr /i "node npm"

# Kill by name (closes all instances)
taskkill /IM node.exe /F
taskkill /IM npm.cmd /F

# PowerShell alternative
Get-Process -Name node, npm -ErrorAction SilentlyContinue | Stop-Process -Force

# Verify they are gone
Get-Process -Name node -ErrorAction SilentlyContinue

Cause 4 – Symlink Creation Without Sufficient Privilege

npm creates symbolic links in node_modules/.bin/ for every package that exposes a CLI binary. On Windows, creating a symlink (as opposed to a junction) requires the SeCreateSymbolicLinkPrivilege. Standard user accounts do not have this privilege by default — only Administrator accounts and accounts with Developer Mode enabled.

npm ERR! code EPERM
npm ERR! syscall symlink
npm ERR! path ..\typescript\bin\tsc
npm ERR! dest C:\project\node_modules\.bin\tsc
npm ERR! errno -4048
npm ERR! Error: EPERM: operation not permitted, symlink
npm ERR!   '..\typescript\bin\tsc' -> 'C:\project\node_modules\.bin\tsc'

Fix A – Enable Windows Developer Mode (recommended)

# Settings → System → For developers → Developer Mode → On
# (No UAC prompt required, persists across reboots)
# After enabling, run npm install normally — no Administrator terminal needed

Fix B – Run terminal as Administrator

# Right-click "Command Prompt" or "PowerShell" → "Run as administrator"
# Then run: npm install

Fix C – Use --no-bin-links (workaround, not a full fix)

# Skips creating .bin symlinks entirely
# CLI tools installed via npm will NOT be directly runnable as commands
npm install --no-bin-links

# You can still run them explicitly via node_modules path:
./node_modules/typescript/bin/tsc --version

Fix D – Use junctions instead (advanced)

NTFS junctions (directory hard links) do not require the symlink privilege and work for directory entries. You cannot tell npm to use junctions automatically, but you can replace broken .bin symlinks with junctions manually for directory targets:

# CMD — create a junction (no admin required, directories only)
mklink /J "node_modules\.bin\myapp" "node_modules\myapp\bin"

# Compare: /D creates a symbolic directory link (requires privilege)
mklink /D "node_modules\.bin\myapp" "node_modules\myapp\bin"  <-- needs Developer Mode

Cause 5 – Corrupted npm Cache

The npm cache stores tarballs and extracted package files in %APPDATA%\npm-cache (Windows) or ~/.npm (Linux/macOS). A partial download, a crashed extraction, or a prior EPERM during a cache write can leave corrupt staging files. Subsequent npm install runs try to use these corrupt entries and fail with EPERM when they attempt the final rename.

# Step 1: clean the npm cache
npm cache clean --force

# Step 2: verify the cache is clean
npm cache verify

# Step 3: delete node_modules and package-lock.json
# Windows Command Prompt:
rd /s /q node_modules
del package-lock.json

# Windows PowerShell:
Remove-Item -Recurse -Force node_modules
Remove-Item package-lock.json

# Cross-platform (handles long paths and locked files better):
npx rimraf node_modules
npx rimraf package-lock.json

# Step 4: reinstall
npm install

Cause 6 – Read-Only File or Directory

Files checked out from version control with read-only attributes, files on a CD-ROM or read-only mount, or files manually marked read-only via Windows Explorer properties will throw EPERM when Node.js tries to write to them. This also affects npm ci which performs a clean reinstall and needs to delete existing node_modules.

Linux / macOS

# Check permissions on the file or directory
ls -la ./node_modules/some-package/index.js

# Remove the read-only attribute
chmod +w ./node_modules/some-package/index.js

# Recursively fix permissions on node_modules
chmod -R u+w node_modules/

# Or fix ownership if root owns the files (common after sudo npm install)
sudo chown -R $(whoami) node_modules/

Windows

# Remove read-only attribute from a single file
attrib -r "node_modules\some-package\index.js"

# Remove read-only attribute recursively from all files in node_modules
attrib -r /s /d "node_modules\*"

# PowerShell — set full control for current user on node_modules
icacls node_modules /grant "%USERNAME%":F /T

# Take ownership first if needed (run as Administrator)
takeown /f node_modules /r /d y
icacls node_modules /grant "%USERNAME%":F /T

Cause 7 – WSL: Project on NTFS Mount (/mnt/c/...)

Running npm install inside WSL (Windows Subsystem for Linux) on a path under /mnt/c/ (or any /mnt/ drive) means Node.js is operating on an NTFS volume accessed via the 9P file system protocol. Symlink creation and certain atomic rename operations over 9P fail with EPERM because the Windows NTFS driver does not support them through this protocol path.

# Bad — project is on the NTFS mount
cd /mnt/c/Users/alice/my-project
npm install
# Error: EPERM: operation not permitted, symlink

# Good — project is on the native WSL ext4 filesystem
cd ~/projects/my-project
npm install   # works correctly

Moving an existing project into WSL filesystem

# From inside WSL:
mkdir -p ~/projects
cp -r /mnt/c/Users/alice/my-project ~/projects/my-project
cd ~/projects/my-project

# Delete the Windows-side node_modules first (they may have Windows-format bin links)
rm -rf node_modules package-lock.json
npm install   # now installs with Linux symlinks
WSL Node.js vs Windows Node.js: Make sure your WSL shell is using the Linux installation of Node.js and npm, not the Windows version. Check with which node — it should return /usr/bin/node or /home/user/.nvm/versions/node/.../bin/node, not /mnt/c/Program Files/nodejs/node. Running the Windows npm binary from WSL on a WSL path is a common source of EPERM and other permission errors.

Cause 8 – npm Cache or Prefix on a Network Share

In corporate environments, Windows roaming profiles often redirect %APPDATA% to a network share. The default npm cache path (%APPDATA%\npm-cache) ends up on that network share, which has higher latency and more restrictive locking semantics. Antivirus on the file server compounds the problem.

# Check where npm is putting its cache and prefix
npm config get cache
npm config get prefix

# If they point to a network share (e.g. \\server\users\alice\...)
# redirect them to a local directory:
npm config set cache "C:\npm-cache"
npm config set prefix "C:\npm-global"

# Create the directories first
mkdir C:\npm-cache
mkdir C:\npm-global

# Add the new prefix to PATH so global binaries remain accessible
# System Properties → Environment Variables → Path → add C:\npm-global\bin

Cause 9 – Windows Long Path Limit (260 characters)

Deeply nested packages in node_modules can exceed Windows' default 260-character path limit (MAX_PATH). When Node.js or npm tries to create a path beyond this limit, Windows returns an error that Node.js maps to EPERM (or ENAMETOOLONG on some configurations).

Enable long paths on Windows 10/11

# Option 1: Group Policy (requires local admin)
# Computer Configuration → Administrative Templates →
# System → Filesystem → Enable Win32 long paths → Enabled

# Option 2: Registry (run PowerShell as Administrator)
Set-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Control\FileSystem" `
  -Name "LongPathsEnabled" -Value 1

# Option 3: Git-managed repos — enable for git operations
git config --system core.longpaths true

Use rimraf for deletion of long-path node_modules

# rimraf handles long paths and retries on EPERM — safer than rd /s /q
npx rimraf node_modules

# Or install globally once:
npm install -g rimraf
rimraf node_modules

Clean Reinstall Patterns

Windows — full clean reinstall

# Step 1: Close VS Code and kill all node processes
taskkill /IM node.exe /F
taskkill /IM npm.cmd /F

# Step 2: Delete node_modules (use rimraf — handles long paths & retries)
npx rimraf node_modules

# Step 3: Delete the lockfile
del package-lock.json

# Step 4: Clean npm cache
npm cache clean --force

# Step 5: Reinstall
npm install

PowerShell — full clean reinstall

# Kill processes
Get-Process node, npm -ErrorAction SilentlyContinue | Stop-Process -Force

# Remove node_modules and lockfile
Remove-Item -Recurse -Force node_modules -ErrorAction SilentlyContinue
Remove-Item -Force package-lock.json -ErrorAction SilentlyContinue

# Clean cache
npm cache clean --force

# Reinstall
npm install

Linux / macOS — clean reinstall

rm -rf node_modules package-lock.json
npm cache clean --force
npm install

WSL — clean reinstall

# Make sure you are in the WSL filesystem, not /mnt/c/...
pwd   # should NOT start with /mnt/

rm -rf node_modules package-lock.json
npm cache clean --force
npm install

Handling EPERM in Code

If your Node.js application performs file system operations that may hit EPERM (for example, writing to a path where another process may hold a lock), catch the error explicitly and distinguish EPERM from other fs errors.

const fs = require('fs/promises');

async function writeFileSafe(filePath, content) {
  try {
    await fs.writeFile(filePath, content, 'utf8');
  } catch (err) {
    if (err.code === 'EPERM') {
      // Operation not permitted — check for read-only flag or process lock
      console.error(
        `EPERM writing to ${filePath}. ` +
        `Check that no other process holds the file open and that it is not read-only.`
      );
      // On Windows: check attrib -r, or retry after a short delay
      throw err;
    }
    if (err.code === 'EACCES') {
      // Permission bits deny access — chmod or chown the file
      console.error(`EACCES writing to ${filePath}. Check file permissions.`);
      throw err;
    }
    throw err; // re-throw all other errors (ENOENT, EMFILE, etc.)
  }
}

// Retry helper — useful on Windows where EPERM is often transient
async function writeFileWithRetry(filePath, content, retries = 3, delayMs = 200) {
  for (let attempt = 1; attempt <= retries; attempt++) {
    try {
      await fs.writeFile(filePath, content, 'utf8');
      return; // success
    } catch (err) {
      if (err.code === 'EPERM' && attempt < retries) {
        console.warn(`EPERM on attempt ${attempt}, retrying in ${delayMs}ms...`);
        await new Promise(resolve => setTimeout(resolve, delayMs));
      } else {
        throw err;
      }
    }
  }
}

Symlink Fix for CI / Docker on Windows

CI pipelines running on Windows agents (GitHub Actions windows-latest, Azure Pipelines Windows agents) need Developer Mode or the symlink privilege to be explicitly granted. The cleanest workaround is to avoid symlinks altogether in CI by using npm ci --ignore-scripts or to enable the privilege in the pipeline definition.

# GitHub Actions — Windows runner, enable Developer Mode equivalent
# The windows-latest runner runs as Administrator, so symlinks work by default.
# If you use a self-hosted Windows runner, add this step:
- name: Enable long paths and symlinks
  shell: powershell
  run: |
    Set-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Control\FileSystem" `
      -Name "LongPathsEnabled" -Value 1
    # Grant symlink privilege to current user (requires admin runner)
    $acl = Get-Acl "C:\Windows\System32\drivers\etc"  # dummy path to load SDDL
    Write-Host "Symlinks enabled for: $env:USERNAME"

# Alternative: skip bin symlinks entirely in CI
- run: npm ci --ignore-scripts
# Dockerfile on Windows base image — Node.js symlinks in node_modules
# Use linux containers whenever possible; Windows containers need explicit privilege grants.
# In a Windows container Dockerfile:
FROM mcr.microsoft.com/windows/servercore:ltsc2022
RUN powershell Set-ItemProperty `
      -Path 'HKLM:\SYSTEM\CurrentControlSet\Control\FileSystem' `
      -Name LongPathsEnabled -Value 1
# ... rest of Dockerfile

Debugging Checklist

  1. Read err.syscallrename/unlink/rmdir = file-handle conflict; symlink = privilege problem; open = read-only or locked file.
  2. Read err.path and err.dest — identifies exactly which file triggered the error.
  3. Close VS Code and all IDEs that have the project open.
  4. Run tasklist | findstr node (Windows) or pgrep -l node (Linux/macOS) to find stuck processes; kill them.
  5. Check if the error is for a symlink syscall — if so, enable Windows Developer Mode.
  6. Add node_modules and %APPDATA%\npm-cache to Windows Defender exclusions.
  7. Run npm config get cache — if it points to a network share, redirect to a local directory.
  8. In WSL, run pwd and confirm you are not on a /mnt/ path.
  9. Run npm cache clean --force then delete node_modules with npx rimraf node_modules.
  10. If all else fails, reboot Windows — this releases all file handles unconditionally — then reinstall.
  11. On Linux/macOS, check for read-only files: ls -la node_modules/some-package/; fix with chmod -R u+w node_modules/.
Avoid sudo npm install: Running sudo npm install on Linux/macOS installs files owned by root into node_modules. Subsequent runs without sudo fail with EACCES or EPERM because the current user cannot modify root-owned files. Fix with sudo chown -R $(whoami) node_modules/ or configure a user-level npm prefix.

Frequently Asked Questions

What is EPERM: operation not permitted in Node.js?

EPERM stands for Error: operation not PERMitted. It is a POSIX system error (errno 1) returned by the OS kernel when a file system operation is refused not because of file permission bits (that is EACCES), but because the operation itself is disallowed — for example, creating a symlink without the required Windows privilege, renaming a file that another process has open, or writing to a read-only filesystem.

What causes EPERM: operation not permitted during npm install on Windows?

The most common causes are: (1) VS Code or another IDE holding a file handle in node_modules while npm tries to rename a staging file; (2) Windows Defender or antivirus locking a file it is scanning at the moment npm renames it; (3) a leftover node.exe process from a crashed prior run; (4) npm trying to create symlinks in .bin without SeCreateSymbolicLinkPrivilege; (5) a corrupted npm cache. Fix: close the IDE, kill node.exe processes, add antivirus exclusions, then do a clean reinstall.

How do I delete node_modules on Windows when I get EPERM?

Use npx rimraf node_modules — rimraf handles long paths and retries on locked files automatically. Alternatively: Remove-Item -Recurse -Force node_modules (PowerShell) or rd /s /q node_modules (Command Prompt). If all three fail, close VS Code, kill all node.exe processes in Task Manager, wait 10 seconds, then retry. A reboot is the last resort — it unconditionally releases all file handles.

How do I fix EPERM symlink errors without running as Administrator?

Enable Windows Developer Mode: Settings → System → For developers → Developer Mode → On. This grants the SeCreateSymbolicLinkPrivilege to your user account without requiring a UAC prompt on each npm install. As a workaround (not a full fix), you can also run npm install --no-bin-links to skip creating symlinks in .bin, but then CLI tools from packages will not be directly runnable as commands.

What is the difference between EPERM and EACCES in Node.js?

EACCES (permission denied) means the process lacks the read, write, or execute permission bits on a specific file or directory — a chmod/chown problem. EPERM (operation not permitted) means the operation itself is forbidden in the current OS context regardless of file permissions — for example, creating a symlink without the required Windows privilege, renaming an open file, or writing to a read-only filesystem. On Windows, EPERM is used broadly for many kernel-level refusals that POSIX would distinguish more granularly.

Why does EPERM happen in WSL during npm install?

In WSL, EPERM occurs when the project is on an NTFS mount (/mnt/c/...) rather than the native WSL ext4 filesystem. Symlink creation and atomic renames over the 9P NTFS protocol fail with EPERM. Fix: move your project to the WSL home directory (~/projects/my-project), delete node_modules and package-lock.json, then run npm install again. Also ensure you are using the WSL Linux Node.js installation, not the Windows one — check with which node.

How do I add node_modules to Windows Defender exclusions?

Via GUI: Windows Security → Virus & threat protection → Manage settings → Exclusions → Add or remove exclusions → Add an exclusion → Folder → select your project root (or the node_modules subfolder). Also exclude %APPDATA%\npm-cache. Via PowerShell (run as Administrator): Add-MpPreference -ExclusionPath "C:\your\project\node_modules" and Add-MpPreference -ExclusionPath "$env:APPDATA\npm-cache".

Why does EPERM happen in CI on Windows but not locally?

Common CI-specific causes: (1) the CI Windows agent user account does not have the symlink privilege — grant it or use npm ci --ignore-scripts; (2) the agent runs antivirus that is not configured with exclusions; (3) the project path is long and exceeds the 260-character limit — enable long paths with the registry key or Group Policy; (4) the npm cache is on a network share — set a local cache path with npm config set cache C:\npm-cache in the pipeline.

Related Errors