Error: ENOSPC in Node.js has two completely different root causes. (1) Inotify watcher limit (Linux only, most common in dev) — the kernel's file-watching slots are exhausted by webpack/Vite/Jest/nodemon watching node_modules. Fix: echo fs.inotify.max_user_watches=524288 | sudo tee -a /etc/sysctl.conf && sudo sysctl -p, then exclude node_modules from your watcher config. (2) Disk space full — the filesystem is genuinely out of space during npm install, a build write, or a Docker layer push. Fix: df -h to confirm, then npm cache clean --force, docker system prune -a, or expand the volume.
What is ENOSPC?
ENOSPC stands for Error NO SPaCe — a
POSIX system error code
that the Linux/macOS kernel emits when a resource quota is exhausted. In Node.js it surfaces as two
distinct error messages that share the same code: 'ENOSPC':
Error: ENOSPC: no space left on device, write '...'— emitted by file I/O syscalls (write,mkdir,open) when the filesystem has no free blocks or inodes left.Error: ENOSPC: System limit for number of file watchers reached, watch '...'— emitted byfs.watchwhen the Linux inotify watch table is full. This has nothing to do with disk space.
The naming is misleading: the inotify variant appears to be about disk space but is actually about an in-kernel
watch-slot quota. Both variants return errno: -28 on Linux because the kernel uses
ENOSPC for any exhausted resource, including the inotify table.
Error: ENOSPC: System limit for number of file watchers reached, watch '/project/src'Watchpack Error (watcher): Error: ENOSPC: System limit for number of file watchers reached, watch '/project'Error: ENOSPC: no space left on device, write '/project/dist/bundle.js'npm ERR! code ENOSPCnpm ERR! errno -28npm ERR! syscall writeError: ENOSPC: no space left on device, mkdir '/tmp/npm-...'
Full Error Examples
Inotify watcher limit variant (most common in development)
Error: ENOSPC: System limit for number of file watchers reached, watch '/home/user/project/node_modules'
at FSWatcher.<anonymous> (node:fs:2341:19)
at FSWatcher.emit (node:events:513:28)
at FSWatcher.start (node:internal/fs/watchers:248:10) {
errno: -28,
code: 'ENOSPC',
syscall: 'watch',
filename: '/home/user/project/node_modules'
}
Disk-full variant (common in CI/CD and containers)
npm ERR! code ENOSPC
npm ERR! syscall write
npm ERR! errno -28
npm ERR! Error: ENOSPC: no space left on device, write '/home/runner/.npm/_cacache/tmp/...'
Error: ENOSPC: no space left on device, write '/project/dist/main.js'
at Object.writeSync (node:fs:714:3)
at writeAllSync (node:fs:413:12) {
errno: -28,
code: 'ENOSPC',
syscall: 'write'
}
Key diagnostic fields on the error object:
| Field | Watcher variant value | Disk variant value | Meaning |
|---|---|---|---|
code | 'ENOSPC' | 'ENOSPC' | Always ENOSPC for both variants |
syscall | 'watch' | 'write', 'mkdir', 'open' | Key differentiator — watch = inotify; write/mkdir = disk full |
errno | -28 | -28 | Linux ENOSPC constant; both variants use -28 |
filename | The watched path | The file being written | Exact path that triggered the error |
err.syscall. If it is 'watch', you have the inotify limit problem — disk space is irrelevant. If it is 'write', 'mkdir', or 'open', run df -h to confirm disk is full.
All Causes at a Glance
| Cause | Platform | Typical trigger | Fix |
|---|---|---|---|
| Linux inotify watch limit exhausted | Linux only | webpack/Vite/Jest/nodemon dev server startup watching node_modules | Raise fs.inotify.max_user_watches via sysctl |
| node_modules included in file watching | Linux | Default chokidar/watchpack config watches entire project tree | Add ignored: /node_modules/ to watcher config |
| Multiple dev servers open simultaneously | Linux | Several terminal tabs each running a dev server | Close other projects; raise inotify limit |
| Docker container inherits low host inotify limit | Linux (Docker) | Dev server inside container fails to watch files | Raise limit on Docker host; use CHOKIDAR_USEPOLLING=true |
| Disk space genuinely full | All platforms | npm install or build write fails with syscall: 'write' | df -h to confirm, then free space |
| npm cache consuming disk | All platforms | CI runner disk fills after many installs | npm cache clean --force |
| Docker overlay2 / image cache full | Linux (Docker host) | Build produces image layers that fill the disk | docker system prune -a |
| Inode exhaustion (disk blocks free but inodes full) | Linux | Huge number of tiny files (e.g. nested node_modules) | df -i to check; remove unnecessary small files |
| tmpfs /tmp too small | Linux | npm writes temp files to an in-memory /tmp filesystem with a small quota | Expand /tmp mount or set npm config set tmp /some/other/path |
| GitHub Actions / GitLab CI disk quota | CI/CD | 14 GB runner disk consumed by pre-installed tools + large node_modules + build artifacts | Remove pre-installed tools before build; use caching |
Root Cause A – Inotify File Watcher Limit (Linux)
This is the most common ENOSPC in Node.js development. On Linux, the kernel's
inotify subsystem allows user-space processes to watch files and directories for
changes. Each watched path consumes one inotify watch slot. The kernel limits
the total number of watch slots per user via the sysctl parameter
fs.inotify.max_user_watches, which defaults to 8192 on most
Linux distributions (Ubuntu 20.04 raised this to 65535, but it is still easily exhausted).
Modern JavaScript tooling — webpack, Vite, Jest, nodemon, Rollup, esbuild, Parcel, and
any tool using chokidar or watchpack — watches every file in
your project tree for changes. A typical project with node_modules contains
50,000 to 200,000 files. Watching even a fraction of those exhausts the default inotify
limit instantly.
Diagnose the inotify limit
# Check the current inotify watch limit
cat /proc/sys/fs/inotify/max_user_watches
# Count how many inotify watches are currently active (all processes)
find /proc/*/fd -lname anon_inode:inotify 2>/dev/null | wc -l
# See which processes are consuming inotify watches
find /proc/*/fd -lname anon_inode:inotify -printf '%hinfo/inotify\n' 2>/dev/null \
| xargs grep -c '^inotify' 2>/dev/null \
| awk -F/ '{print $3, $NF}' \
| sort -k2 -rn | head -10
Fix A-1 – Raise fs.inotify.max_user_watches (permanent)
This is the primary fix. It persists across reboots and takes effect immediately.
Debian, Ubuntu, and most Linux distributions
# Permanent fix — appends to /etc/sysctl.conf and reloads immediately
echo fs.inotify.max_user_watches=524288 | sudo tee -a /etc/sysctl.conf
sudo sysctl -p
# Verify the new value
cat /proc/sys/fs/inotify/max_user_watches
# Expected output: 524288
Arch Linux and systemd-based distributions
# Write to a drop-in file in /etc/sysctl.d/ instead
echo fs.inotify.max_user_watches=524288 | sudo tee /etc/sysctl.d/40-max-user-watches.conf
sudo sysctl --system
# Verify
cat /proc/sys/fs/inotify/max_user_watches
Temporary fix (resets after reboot)
# Effective immediately, no reboot required, but does not survive reboot
sudo sysctl fs.inotify.max_user_watches=524288
Fix A-2 – Exclude node_modules from file watching
Excluding node_modules from your watcher config can reduce the watch count from
100,000+ files down to a few hundred, often eliminating the problem without needing to
raise the sysctl limit. This should be done regardless of whether you hit the limit —
watching node_modules is wasteful and slows down the watcher.
webpack (webpack.config.js)
// CJS — webpack.config.js
module.exports = {
// ...
watchOptions: {
// Ignore node_modules — saves thousands of inotify watches
ignored: /node_modules/,
// Alternatively, use a glob array:
// ignored: ['**/node_modules/**', '**/dist/**', '**/.git/**'],
aggregateTimeout: 300,
},
};
Vite (vite.config.js / vite.config.ts)
// ESM — vite.config.ts
import { defineConfig } from 'vite';
export default defineConfig({
server: {
watch: {
// Exclude directories from inotify watching
ignored: ['**/node_modules/**', '**/dist/**', '**/.git/**'],
},
},
});
Jest (jest.config.js)
// jest.config.js
module.exports = {
watchPathIgnorePatterns: [
'/node_modules/',
'/dist/',
'/build/',
'/.git/',
],
};
nodemon (nodemon.json or package.json)
// nodemon.json
{
"watch": ["src"],
"ignore": ["node_modules", "dist", ".git", "*.test.js"],
"ext": "js,ts,json"
}
// Or inline in package.json:
// "nodemonConfig": { "watch": ["src"], "ignore": ["node_modules"] }
chokidar directly
// CJS
const chokidar = require('chokidar');
const watcher = chokidar.watch('./src', {
// Ignore dotfiles and node_modules
ignored: /(^|[/\\])(\.|node_modules)/,
persistent: true,
depth: 10,
});
watcher.on('change', (filePath) => console.log(`Changed: ${filePath}`));
// Handle ENOSPC gracefully and fall back to polling
watcher.on('error', (error) => {
if (error.code === 'ENOSPC') {
console.error('inotify limit hit — switching to polling mode');
watcher.close();
const pollingWatcher = chokidar.watch('./src', {
ignored: /(^|[/\\])(\.|node_modules)/,
usePolling: true,
interval: 1000,
});
pollingWatcher.on('change', (filePath) => console.log(`Changed: ${filePath}`));
}
});
Fix A-3 – Switch to polling mode as a quick workaround
Polling periodically checks file modification times instead of using inotify watch slots.
It avoids the inotify limit entirely but consumes more CPU. Use it as an immediate workaround
while you apply the permanent sysctl fix.
# For webpack/Create React App via watchpack:
WATCHPACK_POLLING=true npm start
WATCHPACK_POLLING=true npm run dev
# For chokidar-based tools (nodemon, Parcel, Storybook):
CHOKIDAR_USEPOLLING=true npm run dev
CHOKIDAR_USEPOLLING=true nodemon src/index.js
# For nodemon specifically:
nodemon --legacy-watch src/index.js
# Combine both for maximum compatibility:
WATCHPACK_POLLING=true CHOKIDAR_USEPOLLING=true npm run dev
Fix A-4 – Docker containers and WSL
Docker containers share the host kernel. The fs.inotify.max_user_watches value
is a host-kernel setting — you cannot override it inside a Dockerfile or container. You must
set it on the host machine.
# On the Docker host (not inside the container):
echo fs.inotify.max_user_watches=524288 | sudo tee -a /etc/sysctl.conf
sudo sysctl -p
# Then restart your containers — they will inherit the new limit
docker-compose down && docker-compose up
For development containers where you cannot change the host, use polling via environment variables in docker-compose.yml:
# docker-compose.yml
services:
app:
build: .
environment:
# Bypass inotify entirely — use polling instead
- CHOKIDAR_USEPOLLING=true
- WATCHPACK_POLLING=true
- CHOKIDAR_INTERVAL=1000 # polling interval in ms
volumes:
- .:/app
- /app/node_modules # anonymous volume prevents host node_modules from being watched
For WSL 2 (Windows Subsystem for Linux):
# Inside WSL 2 terminal:
echo fs.inotify.max_user_watches=524288 | sudo tee -a /etc/sysctl.conf
sudo sysctl -p
# If sysctl -p fails in WSL 2, restart WSL from PowerShell:
# wsl --shutdown
# wsl
Fix A-5 – GitHub Actions and GitLab CI (inotify variant)
CI runners run on Linux. If your build runs a dev server or a watcher (e.g., Storybook,
Cypress with hot reload, or jest --watch), you may hit the inotify limit.
Add a step before your build to raise it.
# .github/workflows/build.yml
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Increase inotify watch limit
run: |
echo fs.inotify.max_user_watches=524288 | sudo tee -a /etc/sysctl.conf
sudo sysctl -p
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- run: npm ci
- run: npm run build
# .gitlab-ci.yml
build:
script:
- echo fs.inotify.max_user_watches=524288 | sudo tee -a /etc/sysctl.conf
- sudo sysctl -p
- npm ci
- npm run build
---
Root Cause B – Disk Space Exhausted
When the filesystem has no free blocks left, any operation that writes data — file writes,
directory creation, pipe writes — throws ENOSPC: no space left on device.
This is most common during npm install (which extracts hundreds of packages
to disk), build processes that produce large artifacts, and in Docker containers where the
overlay2 storage driver accumulates image layers.
The key differentiator from the inotify variant is err.syscall: disk-full errors
always have syscall: 'write', 'mkdir', or 'open'
— never 'watch'.
Fix B-1 – Diagnose disk usage
# Check free space on all mounted filesystems
df -h
# Sample output when disk is full:
# Filesystem Size Used Avail Use% Mounted on
# /dev/sda1 20G 20G 0 100% / <-- FULL
# Check inode usage (can be full even if disk blocks are free)
df -i
# Find the largest directories in the current tree
du -sh */ 2>/dev/null | sort -hr | head -20
# Find the largest individual files
find . -type f -printf '%s %p\n' 2>/dev/null | sort -rn | head -20
# Check npm cache size
du -sh ~/.npm
Fix B-2 – Free npm and package manager cache
# Clean npm's download cache (safe to delete — npm re-downloads as needed)
npm cache clean --force
# Verify the cache is cleared
npm cache verify
# Remove node_modules and reinstall (also clears old/stale packages)
rm -rf node_modules
npm install
# yarn cache
yarn cache clean
# pnpm store
pnpm store prune
# Find and remove large or duplicate node_modules across a monorepo
npx npkill # interactive UI to select node_modules directories to delete
Fix B-3 – Free Docker disk space
Docker's overlay2 storage driver accumulates image layers, build cache, stopped containers, and dangling images on the host filesystem. On CI runners this is a common cause of ENOSPC during a build.
# See a breakdown of Docker disk usage
docker system df
# Sample output:
# TYPE TOTAL ACTIVE SIZE RECLAIMABLE
# Images 12 3 8.2GB 6.1GB (74%)
# Containers 2 2 1.5GB 0B (0%)
# Build Cache 45 0 3.2GB 3.2GB
# Remove all unused images, stopped containers, and build cache
docker system prune -a
# Remove only dangling (untagged) images
docker image prune
# Remove build cache only (safe to run during active development)
docker builder prune -a
# Remove unused volumes
docker volume prune
# Dockerfile best practices to avoid overlay2 disk bloat
FROM node:20-alpine
WORKDIR /app
# Copy package files first for layer caching
COPY package*.json ./
# Install production dependencies only; clean in the same layer
RUN npm ci --omit=dev && npm cache clean --force
COPY . .
RUN npm run build
# Multi-stage build: copy only the built output to the final image
FROM node:20-alpine AS final
WORKDIR /app
COPY --from=0 /app/dist ./dist
COPY --from=0 /app/node_modules ./node_modules
CMD ["node", "dist/index.js"]
Fix B-4 – GitHub Actions disk space
GitHub Actions ubuntu-latest runners have approximately 14 GB of free disk space.
Pre-installed tools (dotnet, Haskell GHC, Android SDK) can consume several gigabytes. Large
node_modules trees (1–3 GB), Docker image caches, and build artifacts can exhaust
the remaining space.
# .github/workflows/build.yml — free disk space before building
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Free disk space
run: |
# Remove pre-installed tools that your project does not need
sudo rm -rf /usr/share/dotnet
sudo rm -rf /opt/ghc
sudo rm -rf /usr/local/share/boost
sudo rm -rf "$AGENT_TOOLSDIRECTORY"
df -h # confirm freed space
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm' # cache node_modules between runs
- run: npm ci
- run: npm run build
Fix B-5 – GitLab CI disk space
# .gitlab-ci.yml — clean Docker cache between jobs and set artifact expiry
variables:
DOCKER_DRIVER: overlay2
build:
stage: build
before_script:
- docker system prune -af # clean stale Docker layers before build
script:
- npm ci
- npm run build
artifacts:
paths:
- dist/
expire_in: 1 hour # prevent artifacts from accumulating on the runner
Fix B-6 – Inode exhaustion
A filesystem can run out of inodes (directory entries) even when disk blocks are free.
This happens when a huge number of tiny files are created — for example, deeply nested
node_modules or a log-generation bug that creates millions of small files.
The error message is identical to the disk-full variant.
# Check inode usage
df -i
# Sample output showing inode exhaustion:
# Filesystem Inodes IUsed IFree IUse% Mounted on
# /dev/sda1 655360 655360 0 100% / <-- inodes full
# Find directories with the most files (likely culprits)
find . -xdev -printf '%h\n' 2>/dev/null | sort | uniq -c | sort -rn | head -20
# Clean up log files, temp files, or duplicate node_modules
find . -name "*.log" -type f -delete
find . -name ".cache" -type d -exec rm -rf {} + 2>/dev/null || true
Fix B-7 – tmpfs /tmp too small
Some Linux systems (and Docker containers) mount /tmp as a tmpfs
(in-memory filesystem) with a fixed size quota. npm writes temporary files to /tmp
during install. If /tmp is full — even if the main disk has space — you get ENOSPC.
# Check if /tmp is mounted as tmpfs and how full it is
df -h /tmp
mount | grep tmpfs
# Redirect npm's tmp directory to a path with more space
npm config set tmp /var/tmp
# Or set via environment variable
export NPM_CONFIG_TMP=/var/tmp
npm install
Platform and Tool Reference
| Platform | Watcher variant possible? | Disk variant possible? | Notes |
|---|---|---|---|
| Linux (native) | Yes — inotify limit is the most common ENOSPC cause in dev | Yes | Fix watcher: sysctl fs.inotify.max_user_watches=524288. Fix disk: df -h + cleanup. |
| macOS | No — macOS uses FSEvents, no per-user watch limit exists | Yes | ENOSPC on macOS is always disk-full. Check Disk Utility or df -h. |
| Windows | No — uses ReadDirectoryChangesW, no inotify equivalent | Yes (rare) | ENOSPC on Windows is disk-full. Check disk usage in Explorer. |
| Docker (Linux host) | Yes — containers inherit host inotify limit | Yes — overlay2 layers fill the host disk | Set sysctl on host. Use CHOKIDAR_USEPOLLING=true if host is immutable. Run docker system prune -a. |
| WSL 2 | Yes — WSL 2 runs a real Linux kernel with inotify | Yes | Set sysctl inside WSL. Restart WSL with wsl --shutdown if needed. |
| GitHub Actions (ubuntu-latest) | Yes — standard Linux runner | Yes — 14 GB disk, easily consumed by large builds | Add sysctl step for watcher. Remove pre-installed tools for disk space. |
| GitLab CI (shared runners) | Yes — Linux runners | Yes — smaller disk than GitHub Actions on shared runners | Use docker system prune before build. Set artifact expiry. |
Safe Watcher Patterns in Node.js Code
Detect and handle ENOSPC in fs.watch
// CJS
const fs = require('fs');
function watchWithFallback(targetPath, onChange) {
try {
const watcher = fs.watch(targetPath, { recursive: true }, (event, filename) => {
onChange(event, filename);
});
watcher.on('error', (err) => {
if (err.code === 'ENOSPC') {
console.error(
'ENOSPC: inotify watch limit reached. ' +
'Fix: sudo sysctl fs.inotify.max_user_watches=524288'
);
// Fall back to polling
watchWithPolling(targetPath, onChange);
} else {
throw err;
}
});
return watcher;
} catch (err) {
if (err.code === 'ENOSPC') {
console.error('ENOSPC on watch setup — falling back to polling');
return watchWithPolling(targetPath, onChange);
}
throw err;
}
}
function watchWithPolling(targetPath, onChange) {
const chokidar = require('chokidar');
return chokidar.watch(targetPath, {
usePolling: true,
interval: 1000,
ignored: /node_modules/,
}).on('change', (filePath) => onChange('change', filePath));
}
const watcher = watchWithFallback('./src', (event, filename) => {
console.log(`${event}: ${filename}`);
});
Handle ENOSPC during file writes
// ESM
import { promises as fs } from 'fs';
import path from 'path';
async function writeFileSafe(filePath, content) {
try {
await fs.mkdir(path.dirname(filePath), { recursive: true });
await fs.writeFile(filePath, content, 'utf8');
} catch (err) {
if (err.code === 'ENOSPC') {
// Provide a clear actionable error message
throw new Error(
`ENOSPC: Disk full — cannot write to ${filePath}. ` +
`Run 'df -h' to check disk usage and free space before retrying.`
);
}
throw err;
}
}
await writeFileSafe('./dist/bundle.js', bundleContent);
Debugging Checklist
- Read
err.syscall: if it is'watch'you have the inotify variant; if it is'write','mkdir', or'open'you have the disk variant. - For the inotify variant: run
cat /proc/sys/fs/inotify/max_user_watches— if the output is 8192 or 65535, raise it immediately withsudo sysctl fs.inotify.max_user_watches=524288. - For the disk variant: run
df -hto confirm the filesystem is at 100% usage and identify which mount is full. - Run
df -ito check inode exhaustion — a filesystem can run out of inodes even when disk blocks are free. - Check whether
node_modulesis being watched — addignored: /node_modules/to your watcher config to eliminate thousands of unnecessary watch slots. - If you are on Docker: remember inotify limits come from the host kernel; set
sysctlon the host or useCHOKIDAR_USEPOLLING=truein the container environment. - If you are on macOS or Windows and see ENOSPC: it is always disk-full — check disk space in your OS disk utility.
- In CI (GitHub Actions, GitLab): add the
sysctlstep for inotify; remove pre-installed tools or add adocker system prunestep for disk. - Run
npm cache clean --forceand checkdu -sh ~/.npm— the npm cache can grow to several gigabytes. - As a quick test, try
WATCHPACK_POLLING=true CHOKIDAR_USEPOLLING=true npm run dev— if the error disappears, the cause was definitely the inotify limit.
df -h, and let the operator fix the actual underlying resource problem.
Frequently Asked Questions
What is ENOSPC in Node.js?
ENOSPC is a POSIX error code meaning "Error NO SPaCe". In Node.js it appears as two distinct messages: Error: ENOSPC: no space left on device (disk full or inotify table full) and Error: ENOSPC: System limit for number of file watchers reached (Linux inotify limit exceeded). Both share code: 'ENOSPC' and errno: -28 but have different causes and fixes. Check err.syscall: 'watch' = inotify limit; 'write'/'mkdir' = disk full.
How do I fix Error: ENOSPC: System limit for number of file watchers reached?
This is a Linux inotify limit. The permanent fix is: echo fs.inotify.max_user_watches=524288 | sudo tee -a /etc/sysctl.conf && sudo sysctl -p. Then restart your dev server. Also exclude node_modules from watching in your tool's config (e.g. watchOptions.ignored: /node_modules/ in webpack, server.watch.ignored in Vite). For an immediate workaround without sudo access, set WATCHPACK_POLLING=true or CHOKIDAR_USEPOLLING=true.
How do I fix Error: ENOSPC: no space left on device during npm install?
Run df -h to confirm the disk is actually full. Then free space: npm cache clean --force removes the npm download cache (safe to delete). rm -rf node_modules removes installed packages (reinstall with npm install). On Docker hosts, docker system prune -a removes unused images and build cache. Use du -sh */ | sort -hr | head -20 to find the largest directories consuming space.
Why does ENOSPC happen with webpack, Vite, Jest, or nodemon?
These tools use chokidar or watchpack to monitor every file in your project for changes. On Linux, each watched path consumes one inotify watch slot — a kernel resource tracked separately from disk space. The default limit is 8192 watches on many distros, while a typical project with node_modules has 50,000–200,000 files. Raising fs.inotify.max_user_watches to 524288 and excluding node_modules from the watcher config permanently fixes the issue.
Does ENOSPC: System limit for number of file watchers reached happen on macOS or Windows?
No. The inotify watcher variant is Linux-only. macOS uses FSEvents (no comparable per-user watch limit) and Windows uses ReadDirectoryChangesW (no inotify equivalent). If you see ENOSPC on macOS or Windows it is always the disk-full variant — check df -h (macOS/Linux terminal) or Disk Utility / Windows Explorer on macOS/Windows.
How do I fix ENOSPC in a Docker container?
For the watcher variant: inotify limits are set by the host kernel, not the container. Run echo fs.inotify.max_user_watches=524288 | sudo tee -a /etc/sysctl.conf && sudo sysctl -p on the host machine, then restart your containers. If you cannot modify the host, set CHOKIDAR_USEPOLLING=true and WATCHPACK_POLLING=true as environment variables in your docker-compose.yml. For the disk variant: run docker system prune -a on the host to remove unused images and build cache.
How do I fix ENOSPC on GitHub Actions or GitLab CI?
For the inotify watcher variant, add a step before your build: echo fs.inotify.max_user_watches=524288 | sudo tee -a /etc/sysctl.conf && sudo sysctl -p. For disk-full on GitHub Actions (14 GB runner disk), remove pre-installed tools first: sudo rm -rf /usr/share/dotnet /opt/ghc /usr/local/share/boost. For GitLab, use docker system prune -af in before_script and set short artifact expiry.
What is the difference between ENOSPC and EMFILE?
EMFILE ("too many open files") is a per-process file descriptor limit — raised with ulimit -n 65536. ENOSPC watcher variant is a Linux inotify watch-slot limit — raised with sysctl fs.inotify.max_user_watches. Both can surface during webpack/Vite startup but are separate resources with separate limits and separate fixes. EMFILE is about open file handles; ENOSPC watcher variant is about kernel inotify slots. You can hit both simultaneously if your project is large enough.
Why does ENOSPC happen in production but not locally?
Several reasons: (1) CI/CD runners have limited disk (GitHub Actions: ~14 GB) while developer laptops have hundreds of GB; (2) production servers or containers run Linux where the inotify watcher limit applies, while local development may be on macOS (FSEvents, no limit); (3) Docker build cache and old image layers accumulate on CI runners; (4) WSL 2 on Windows is a Linux environment where inotify limits do apply even though the host OS is Windows.
Related Errors
EMFILE: too many open files— per-process file descriptor limit; often appears alongside ENOSPC watcher variant when both inotify and ulimit are exhaustedENOENT: no such file or directory— the path does not exist; distinct from ENOSPC where the disk or inotify resource is fullEADDRINUSE: address already in use— port binding failure; can appear in the same CI environment when disk and port resource constraints compoundEPIPE: broken pipe— write to a closed pipe; can appear when a disk-full ENOSPC causes a write stream to be destroyed mid-pipeENFILE: file table overflow— system-wide fd table exhausted (all processes combined); rarer than EMFILE; requiressysctl fs.file-maxtuningJavaScript heap out of memory— another resource exhaustion error that commonly appears in the same large-build scenarios as disk-full ENOSPC