{"record":{"id":"79a6b8ec09c1c581","repo":"denoland/deno","slug":"err-use-after-close","errorCode":"ERR_USE_AFTER_CLOSE","errorMessage":"readline was closed","messagePattern":"readline was closed","errorType":"exception","errorClass":"ERR_USE_AFTER_CLOSE","httpStatus":null,"severity":"error","filePath":"ext/node/polyfills/internal/readline/interface.mjs","lineNumber":437,"sourceCode":"\n  /**\n   * Writes the configured `prompt` to a new line in `output`.\n   * @param {boolean} [preserveCursor]\n   * @returns {void}\n   */\n  prompt(preserveCursor) {\n    if (this.paused) this.resume();\n    if (this.terminal && op_get_env_no_permission_check(\"TERM\") !== \"dumb\") {\n      if (!preserveCursor) this.cursor = 0;\n      this[kRefreshLine]();\n    } else {\n      this[kWriteToOutput](this[kPrompt]);\n    }\n  }\n\n  [kQuestion](query, cb) {\n    if (this.closed) {\n      throw new ERR_USE_AFTER_CLOSE(\"readline\");\n    }\n    if (this[kQuestionCallback]) {\n      this.prompt();\n    } else {\n      this[kOldPrompt] = this[kPrompt];\n      this.setPrompt(query);\n      this[kQuestionCallback] = cb;\n      this.prompt();\n    }\n  }\n\n  [kOnLine](line) {\n    if (this[kQuestionCallback]) {\n      const cb = this[kQuestionCallback];\n      this[kQuestionCallback] = null;\n      this.setPrompt(this[kOldPrompt]);\n      cb(line);\n    } else {","sourceCodeStart":419,"sourceCodeEnd":455,"githubUrl":"https://github.com/denoland/deno/blob/9ad36f7a2cce60488e6ec52283efb32efddaf93a/ext/node/polyfills/internal/readline/interface.mjs#L419-L455","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\nrl.on('SIGINT', () => rl.close());\nsetTimeout(() => rl.question('again? ', cb), 1000); // may fire after close\n\n// after\nrl.on('SIGINT', () => rl.close());\nsetTimeout(() => { if (!rl.closed) rl.question('again? ', cb); }, 1000);","handlingStrategy":"validation","validationCode":"function ask(rl: readline.Interface, q: string): Promise<string> | null {\n  if (rl.closed) return null; // caller should recreate the interface\n  return new Promise((res) => rl.question(q, res));\n}","typeGuard":"const isOpen = (rl: { closed: boolean }): boolean => !rl.closed;","tryCatchPattern":"try {\n  rl.question(q, cb);\n} catch (err) {\n  if (err?.code === 'ERR_USE_AFTER_CLOSE') {\n    rl = readline.createInterface({ input, output });\n    rl.question(q, cb);\n  } else throw err;\n}","preventionTips":["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"],"tags":["readline","node-compat","deno","lifecycle","use-after-close"],"backgroundTag":"use-after-close","analyzedSha":"9ad36f7a2cce60488e6ec52283efb32efddaf93a","analyzedAt":"2026-08-20T13:07:44.778Z","schemaVersion":2},"datasetVersion":"2026-08-26T04:18:47.238Z"}