Short answer
If the message ends in watch, the file watcher is the problem: install Watchman (brew install watchman) for Jest and React Native, or exclude node_modules from the watcher. If it ends in open, your code opens too many files at once: limit how many run in parallel and make sure every file handle and stream is closed. Raising the limit with ulimit -n helps as a stopgap but does not fix a leak.
What is using up file descriptors, and the fix
01
Thousands of files opened in parallel
“Maximum number of file descriptors allowable on the system has been reached, and requests for another descriptor cannot be fulfilled until at least one has been closed.” Node.js, Errors: common system errors
Promise.all over a large list of fs.readFile calls opens them all at once. Process them in small batches instead.
for (let i = 0; i < files.length; i += 50) { await Promise.all(files.slice(i, i + 50).map((f) => fs.promises.readFile(f))); }02
File watchers (EMFILE ... watch)
Jest in watch mode, Metro for React Native and some dev servers watch every file in the project. Without Watchman, the Node.js fallback watcher can open a descriptor for every watched file or folder, depending on the platform. Install Watchman, and keep node_modules and build folders out of the watch list.
brew install watchman03
File handles that are never closed
“It is the application's responsibility to close it and make sure there's no file descriptor leak.” Node.js, fs: file handles and streams
Opening files with fs.promises.open and never closing them, or streams that are never consumed or destroyed, leak descriptors until the limit is hit, usually after the app has run for a while. Close handles in a finally block.
const fh = await fs.promises.open(file); try { /* read */ } finally { await fh.close(); }04
A low limit for the process
“To remedy a low limit, run ulimit -n 2048 in the same shell that will run the Node.js process.” Node.js, Errors: common system errors
Check the current limit and raise it for the shell that starts Node.js. This is a stopgap for a real workload, not a fix for a leak.
ulimit -n # current limit ulimit -n 2048 # raise it for this shell
Checked against the vendor’s docs on : Node.js: Errors (common system errors); Node.js: File system.
Rather hand it over?
Fixed scope, fixed price, paid upfront. The work happens in writing; no call needed.
- from $450App Fix TicketOne reproducible bug in an existing mobile or web app, diagnosed and fixed.See scope and start
- from $450AI-Built App RescueA Lovable, Bolt or Cursor app checked end to end in 3 days, then its worst problems fixed at a fixed price.No issues worth fixing, no charge. If the Launch Check finds nothing rated medium or higher, Simon refunds the full $450. You keep the report either way. This applies to the first 10 Launch Checks bought after October 30, 2026.See scope and start
Related: Node.js “ENOENT: no such file or directory” · Node.js “JavaScript heap out of memory” · npm “EACCES: permission denied” · Vercel ERR_TOO_MANY_REDIRECTS