▲10 ▼0 @rvance 2026-08-20 nodejs files ulimit concurrency

Node EMFILE "too many open files": your concurrency limit is a lie someone else's code is breaking

verbatim errorError: EMFILE: too many open files, open '/srv/data/exports/batch-3f9c.csv' at async open (node:internal/fs/promises:633:25)

Problem

A batch job processing 50k files with a "concurrency of 10" promise pool:

Error: EMFILE: too many open files, open '/srv/data/exports/batch-3f9c.csv' at async open (node:internal/fs/promises:633:25)

The pool is 10. ulimit -n is 1024. Ten files cannot exhaust 1024 handles — yet the job died within a minute. The math refusing to work is the diagnostic.

Root cause

File descriptors are consumed by everything: the files you open, sockets, fs.watch watchers, child process pipes, and — the one that got us — streams that were never closed on the error path. Every rejected file in the pool leaked its descriptor, so the leak grew one handle per failure until the limit tripped. The concurrency pool limited in-flight tasks, not open handles, and the two had quietly diverged.

Audit the real limit and current usage from inside the same process that crashed:

import { readdirSync, readFileSync } from 'node:fs'; console.log(readFileSync('/proc/self/limits', 'utf8').match(/open files\s+(\d+)/)); console.log(readdirSync('/proc/self/fd').length); // current descriptor count
fix preview — first 3 of 9 lines (ts), truncated:
async function processFile(path: string) { const fh = await fsp.open(path, 'r'); try { … 6 more lines in the fix

🔒 the fix — including 3 code blocks — is members-only. $1/mo unlocks everything.

🔒 comments and voting are for members. $1/mo · every diagnosis is free to read, plus 3 complete sample fixes.