denoland/deno · error · Error
ERR_DIR_CLOSED
ERR_DIR_CLOSED
Error message
Directory handle was closed
What it means
ERR_DIR_CLOSED ('Directory handle was closed') is thrown by Dir.readSync() and the async read path after close()/closeSync() has been called on the handle. The class tracks a #closed flag; any read attempt afterwards fails before touching the filesystem. The error carries code ERR_DIR_CLOSED, matching Node.
Solutions
- Set the handle reference to null after closing and check it before every read
- Pair opendir with exactly one consumer - the scope that reads is the scope that closes
- For early exit use break and let the owning scope perform close()
Example fix
// before
const dir = fs.opendirSync(path);
for (;;) {
const ent = dir.readSync();
if (!ent) break;
if (ent.name === 'stop') dir.closeSync(); // then loop calls readSync again
}
// after
const dir = fs.opendirSync(path);
let closed = false;
try {
for (;;) {
const ent = dir.readSync();
if (!ent) break;
if (ent.name === 'stop') break;
}
} finally {
if (!closed) { dir.closeSync(); closed = true; }
} Defensive patterns
Strategy: validation
Validate before calling
class SafeDir {
#dir: fs.Dir<string>;
#closed = false;
constructor(path: string) { this.#dir = fs.opendirSync(path); }
readSync() {
if (this.#closed) throw new Error('dir already closed');
return this.#dir.readSync();
}
closeSync() {
if (!this.#closed) { this.#dir.closeSync(); this.#closed = true; }
}
} Try / catch
try {
const entry = dir.readSync();
} catch (e) {
if ((e as NodeJS.ErrnoException).code === 'ERR_DIR_CLOSED') {
break; // iteration finished by another path - stop cleanly
}
throw e;
} Prevention
- Close directory handles exactly once, in the same scope that iterates them
- Use break for early exit and let the owning scope close the handle
- Check a closed flag before every read when handles are shared across helper functions
When it happens
Trigger: const d = fs.opendirSync(dir); d.closeSync(); d.readSync(); a cleanup/error path closing a shared handle while another loop still reads it; double iteration where the first consumer closes the handle.
Common situations: Helper functions that close handles on error while their caller continues iterating; early-break loops followed by a finally that reads once more; refactors that move close() earlier in the lifecycle.
Related errors
AI-assisted analysis of denoland/deno@9ad36f7a2c (2026-08-20).
Data as JSON: /api/errors/3f4405b2d492c8c6.
Report an issue: GitHub.
Appendix: source
Thrown at ext/node/polyfills/_fs/_fs_dir.ts:96
iteratorResult.done
? null
: direntFromDeno(iteratorResult.value, this.#dirPath),
);
}
},
(err) => {
if (callback) {
callback(err);
}
reject(err);
},
);
});
}
readSync(): Dirent | null {
if (this.#closed) {
throw new ERR_DIR_CLOSED();
}
if (!this.#syncIterator) {
this.#syncIterator = Deno.readDirSync(this.path)![SymbolIterator]();
}
const iteratorResult = ArrayIteratorPrototypeNext(this.#syncIterator);
if (iteratorResult.done) {
return null;
} else {
return direntFromDeno(iteratorResult.value, this.#dirPath);
}
}
/**
* Unlike Node, Deno does not require managing resource ids for reading
* directories, and therefore does not need to close directories when
* finished reading.
*/View on GitHub (pinned to 9ad36f7a2c)