This error occurs when Node.js fs.watch() reaches the system limit on file descriptors or inode watchers. The file watching subsystem cannot monitor additional files because the system has exhausted its available watching capacity.
This error indicates that Node.js's fs.watch() function has exhausted the operating system's capacity to monitor file system changes. Unlike disk space issues, this refers to the system limit on inode watchers or file descriptor watches. On Linux systems, each watched file consumes a slot in the kernel's inotify watch limit (controlled by /proc/sys/fs/inotify/max_user_watches). When this limit is reached, fs.watch() fails with ENOSPC. On macOS and Windows, similar limitations exist through their respective file system event APIs (FSEvents and ReadDirectoryChangesW). This commonly occurs in development environments with build tools, IDE watchers, or applications monitoring large directory trees.
First, verify what your current inotify watch limit is:
cat /proc/sys/fs/inotify/max_user_watchesThe default is typically 8192 on many distributions. If you see a low number (less than 10000), this is likely your issue.
Increase the limit by editing the sysctl configuration:
# Check current value
sudo sysctl fs.inotify.max_user_watches
# Set it to a higher value (e.g., 100000)
sudo sysctl -w fs.inotify.max_user_watches=100000To make this permanent across reboots, add it to /etc/sysctl.conf:
# Edit the sysctl configuration
sudo nano /etc/sysctl.conf
# Add or update this line
fs.inotify.max_user_watches=100000
# Apply the changes
sudo sysctl -pFor Docker/containerized environments:
RUN echo fs.inotify.max_user_watches=100000 | tee -a /etc/sysctl.confOr run the container with sysctls:
docker run --sysctl fs.inotify.max_user_watches=100000 your-imageConfigure your build tools to watch fewer files or use polling instead of inotify:
webpack/webpack-dev-server:
// webpack.config.js
module.exports = {
watchOptions: {
ignored: ['node_modules', '.git', 'dist', 'build'],
aggregateTimeout: 300,
poll: false,
},
};Next.js:
// next.config.js
module.exports = {
onDemandEntries: {
maxInactiveAge: 25 * 1000,
pagesBufferLength: 5,
},
};Vite:
// vite.config.js
export default {
server: {
watch: {
ignored: ['node_modules', '.git', 'dist'],
usePolling: false,
},
},
};parcel:
// .parcelrc
{
"watchIgnore": ["node_modules", ".git", "dist"],
}If increasing limits doesn't work, fall back to polling-based watching:
// For webpack
watchOptions: {
poll: 1000, // Poll every 1000ms instead of using inotify
aggregateTimeout: 300,
}
// For custom fs.watch usage
fs.watch(path, { recursive: true }, (eventType, filename) => {
// Your handler
});
// Fallback pattern
const watcher = fs.watch(path, (eventType, filename) => {
// Handler
});
watcher.on('error', (err) => {
if (err.code === 'ENOSPC') {
console.log('Falling back to fs.watchFile (polling)');
fs.watchFile(path, { interval: 1000 }, (curr, prev) => {
// Your polling handler
});
}
});Polling is less efficient but works when inotify is exhausted.
Exclude unnecessary directories from watching:
// Generic watch with exclusions
function watchDirectory(dir, callback) {
const ignoredDirs = new Set([
'node_modules',
'.git',
'.next',
'dist',
'build',
'.cache',
'coverage',
'.pytest_cache',
]);
function watch(path) {
const basename = require('path').basename(path);
if (ignoredDirs.has(basename)) {
return;
}
fs.watch(path, { recursive: true }, callback);
}
watch(dir);
}For monorepos, only watch packages you're actively developing:
// In your lerna.json or package.json workspaces
{
"workspaces": {
"packages": ["packages/*/src"],
"ignoreChanges": ["**/node_modules/**", "**/*.test.ts", "**/dist/**"]
}
}macOS and Windows don't have the same inotify mechanism, but they have equivalent limitations:
macOS FSEvents:
The system has a limit on open file descriptors. Check and increase if needed:
# Check soft limit
ulimit -n
# Increase soft limit for current session
ulimit -n 10240
# Make permanent (add to ~/.zshrc or ~/.bash_profile)
ulimit -n 10240Windows ReadDirectoryChangesW:
Windows has a limit on change notifications per directory. If experiencing issues:
- Use polling via watchOptions poll flag
- Exclude unnecessary directories
- Reduce watch scope to specific file patterns
For all platforms, use the watchFile API as a fallback when watch fails:
const watchPath = (path, callback) => {
fs.watch(path, callback).on('error', (err) => {
if (err.code === 'ENOSPC' || err.code === 'EMFILE') {
console.log('Using fs.watchFile fallback...');
fs.watchFile(path, { interval: 1000 }, callback);
}
});
};Understanding inotify architecture: Linux's inotify system uses a single resource pool per user, not per process. If multiple Node.js processes, Docker containers, or IDE watchers run as the same user, they all share the same 8192 (or custom) watch limit. This is why monorepo development with multiple watchers often hits limits first.
Real-world examples:
- A typical Create React App project watches ~2000-3000 files
- A monorepo with 10 packages can watch 20,000+ files
- node_modules alone can contain 100,000+ files
Temporary overrides: You can also temporarily set limits for a single process:
# Set limit for a specific process
prlimit --nofile=10240 --nproc=10240 npm startContainer memory: In Docker, inotify watches are still limited by the host system limits even if you set them in the container. The limit must be increased on the host or in compose/k8s configuration.
Monitoring watch usage: Debug which process is using watches:
# Show inotify usage per process on Linux
find /proc -name wd_list -exec wc -l {} \;
# Or use lsof to see all watches
lsof | grep inotifyPerformance consideration: Increasing max_user_watches to very high values (e.g., 1000000) can impact system performance. Balance between your needs and system resources. Typical production settings are 50000-100000.
Error: EMFILE: too many open files, watch
EMFILE: fs.watch() limit exceeded
Error: Middleware next() called multiple times (next() invoked twice)
Express middleware next() called multiple times
Error: Worker failed to initialize (worker startup error)
Worker failed to initialize in Node.js
Error: EMFILE: too many open files, open 'file.txt'
EMFILE: too many open files
Error: cluster.fork() failed (cannot create child process)
cluster.fork() failed - Cannot create child process