denoland/deno · error · ERR_USE_AFTER_CLOSE
ERR_USE_AFTER_CLOSE
ERR_USE_AFTER_CLOSE
Error message
readline was closed
What it means
The internal kQuestion path backs rl.question(); once rl.close() has run (closed = true, 'close' emitted), calling question throws ERR_USE_AFTER_CLOSE('readline'). The interface cannot queue a question callback on a closed input stream, so this is a hard programmer error, not a race.
Source
Thrown at ext/node/polyfills/internal/readline/interface.mjs:437
/**
* Writes the configured `prompt` to a new line in `output`.
* @param {boolean} [preserveCursor]
* @returns {void}
*/
prompt(preserveCursor) {
if (this.paused) this.resume();
if (this.terminal && op_get_env_no_permission_check("TERM") !== "dumb") {
if (!preserveCursor) this.cursor = 0;
this[kRefreshLine]();
} else {
this[kWriteToOutput](this[kPrompt]);
}
}
[kQuestion](query, cb) {
if (this.closed) {
throw new ERR_USE_AFTER_CLOSE("readline");
}
if (this[kQuestionCallback]) {
this.prompt();
} else {
this[kOldPrompt] = this[kPrompt];
this.setPrompt(query);
this[kQuestionCallback] = cb;
this.prompt();
}
}
[kOnLine](line) {
if (this[kQuestionCallback]) {
const cb = this[kQuestionCallback];
this[kQuestionCallback] = null;
this.setPrompt(this[kOldPrompt]);
cb(line);
} else {View on GitHub (pinned to 9ad36f7a2c)
Solutions
- Check rl.closed before asking: if (rl.closed) return; (or recreate the interface)
- Wait for the current question's callback/answer before closing on signals
- Create a fresh createInterface per prompt session instead of reusing a closed one
- Use readline/promises question() and await it before any close path runs
Example fix
// before
rl.on('SIGINT', () => rl.close());
setTimeout(() => rl.question('again? ', cb), 1000); // may fire after close
// after
rl.on('SIGINT', () => rl.close());
setTimeout(() => { if (!rl.closed) rl.question('again? ', cb); }, 1000); Defensive patterns
Strategy: validation
Validate before calling
function ask(rl: readline.Interface, q: string): Promise<string> | null {
if (rl.closed) return null; // caller should recreate the interface
return new Promise((res) => rl.question(q, res));
} Type guard
const isOpen = (rl: { closed: boolean }): boolean => !rl.closed; Try / catch
try {
rl.question(q, cb);
} catch (err) {
if (err?.code === 'ERR_USE_AFTER_CLOSE') {
rl = readline.createInterface({ input, output });
rl.question(q, cb);
} else throw err;
} Prevention
- Check rl.closed before every question() in async or timer-driven code
- Create a fresh interface per session instead of reusing a closed one
- Resolve or cancel pending questions before handling SIGINT/close
When it happens
Trigger: rl.question('Name?', cb) after rl.close(); asking inside an event handler that fires after close; a REPL loop that closes the interface on 'exit'/'SIGINT' but a pending setTimeout or promise resolution then calls question again.
Common situations: Prompt queues that keep asking questions while an abort/SIGINT handler already closed the rl; sequential async ask() helpers that race user Ctrl+C against the next question; reusing one interface across sessions instead of recreating it.
Related errors
- ERR_HTTP_HEADERS_SENT
- ERR_INVALID_ARG_VALUE
- ERR_INVALID_CURSOR_POS
- ERR_INVALID_ARG_VALUE
- ERR_INVALID_ARG_TYPE
AI-assisted analysis of denoland/deno@9ad36f7a2c (2026-08-20).
Data as JSON: /api/errors/79a6b8ec09c1c581.
Report an issue: GitHub.