How to Fix Node.js EBUSY: resource busy or locked Errors on Windows

intermediate๐Ÿ’š Node.js2026-06-17| Windows 10/11, Node.js v14+, fs module

Error Message

Error: EBUSY: resource busy or locked, unlink 'path/to/file'
#nodejs#fs#file-system#windows

The Error Message

If you've spent any time developing Node.js apps on Windows, you've likely hit a wall with the EBUSY error. It usually pops up just when you try to delete, rename, or move a file, leaving you wondering why a simple operation is suddenly forbidden:

Error: EBUSY: resource busy or locked, unlink 'C:\project\data\temp.txt'

Why This Happens

Technically, EBUSY isn't a Node.js bug; it's a direct message from the Windows operating system. Unlike Linux or macOS, which often let you delete files even while they're open, Windows enforces strict file locking. If any process holds an open "handle" to that file, the OS will reject your request to modify it.

Frequent causes include:

  • Active Streams: A file stream (read or write) that wasn't properly closed with .end().
  • Editor Indexing: VS Code or IntelliJ indexing your project files in the background.
  • Antivirus Scans: Windows Defender or third-party tools scanning a file the millisecond it's created.
  • System Services: The Windows Search Indexer or a File Explorer preview pane holding the file open.
  • Race Conditions: Trying to delete a file while a previous write operation is still flushing to disk.

Step-by-Step Fixes

1. Explicitly Close Your Streams

Forgetting to close a stream is the most common reason for EBUSY. Even if you've finished writing data, Node.js might keep the file handle open until garbage collection kicks in. This can take several seconds. To be safe, always wait for the close event before attempting to delete the file.

const fs = require('fs');

const stream = fs.createWriteStream('example.txt');
stream.write('Data for processing');

// Signal the end of writing
stream.end();

// Only delete once we are 100% sure the OS has released the lock
stream.on('close', async () => {
  try {
    await fs.promises.unlink('example.txt');
    console.log('File deleted successfully');
  } catch (err) {
    console.error('EBUSY persistent:', err);
  }
});

2. Implement a Smart Retry Loop

Since external locks (like antivirus scans) are usually transient, they often last less than 50ms. A simple retry mechanism can bypass these "blips" and keep your app running smoothly without crashing.

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

async function safeUnlink(filePath, retries = 5, delay = 100) {
  for (let i = 0; i  setTimeout(res, delay));
        continue;
      }
      throw err;
    }
  }
}

3. Use graceful-fs

If your application performs heavy file I/O, consider using graceful-fs. It's a battle-tested drop-in replacement for the native fs module used by npm and jest. It automatically retries operations that fail due to transient OS errors like EBUSY or EMFILE.

// Install: npm install graceful-fs
const fs = require('graceful-fs');

fs.unlink('path/to/file', (err) => {
  if (err) throw err;
  console.log('Successfully deleted');
});

4. Identify the Locking Process

If your code is clean but the error persists, another program is definitely holding the lock. You can use the Windows Resource Monitor to find the culprit:

  • Press Win + R, type resmon, and press Enter.
  • Navigate to the CPU tab.
  • In the Associated Handles search box, type your filename (e.g., temp.txt).
  • The list will show exactly which process (like MsMpEng.exe for Defender or Everything.exe) is the owner.

Verification and Prevention

To ensure your fix is solid, run a 100x Stress Test: wrap your file operation in a loop and execute it 100 times in rapid succession. Race conditions often hide during single manual runs but appear instantly under load.

To prevent these issues in the future:

  • Avoid Sync methods: fs.unlinkSync blocks the event loop and is more prone to locking conflicts than fs.promises.
  • Exclude Temp Folders: If you generate thousands of small logs, put them in a dedicated folder and exclude that path from your antivirus real-time protection.
  • Cleanup First: Always call .destroy() or .close() on streams before unlinking.

Related Error Notes