denoland/deno · error
Failed to create editor.
Error message
Failed to create editor.
What it means
op_read_line_prompt backs Deno.prompt(message, defaultValue): when stdin is a terminal, Deno builds a rustyline Editor so the default value is editable inline. Editor::new() returns an error when the terminal cannot be initialized (no usable TTY/termios environment), and this expect() panics. So the JS-side isTerminal() check can pass while rustyline still fails — e.g. a PTY-attached stdin in a container without a usable controlling terminal or /dev/tty.
Source
Thrown at runtime/ops/tty.rs:501
_: &Event,
_: RepeatCount,
_: bool,
_: &EventContext,
) -> Option<Cmd> {
self.interrupted_by_esc.store(true, Relaxed);
Some(Cmd::Interrupt)
}
}
#[op2]
#[string]
pub fn op_read_line_prompt(
#[string] prompt_text: &str,
#[string] default_value: &str,
) -> Result<Option<String>, JsReadlineError> {
let _terminal_input_guard = deno_permissions::prompter::lock_terminal_input();
let mut editor = Editor::<(), rustyline::history::DefaultHistory>::new()
.expect("Failed to create editor.");
editor.set_keyseq_timeout(Some(1));
let interrupted_by_esc = Arc::new(AtomicBool::new(false));
editor.bind_sequence(
KeyEvent(KeyCode::Esc, Modifiers::empty()),
EventHandler::Conditional(Box::new(PromptEscEventHandler {
interrupted_by_esc: interrupted_by_esc.clone(),
})),
);
let read_result =
editor.readline_with_initial(prompt_text, (default_value, ""));
match read_result {
Ok(line) => Ok(Some(line)),
Err(ReadlineError::Interrupted) => {
if interrupted_by_esc.load(Relaxed) {
return Ok(None);
}View on GitHub (pinned to 9ad36f7a2c)
Solutions
- Run the script in a real interactive terminal (a proper login shell/SSH session with a controlling TTY).
- Guard with a fallback: only call Deno.prompt(message, default) when `Deno.stdin.isTerminal()`, and additionally be prepared for exotic environments by supporting a plain `Deno.prompt(message)` path or reading stdin lines yourself.
- Set a sane TERM (e.g. `TERM=xterm-256color`) in containerized/pty environments before running.
- In CI/automation, avoid the default-value prompt form entirely; accept flags or env vars instead.
Example fix
// before
const name = Deno.prompt("Your name", "stranger"); // panics in pty-less container
// after
const name = Deno.stdin.isTerminal()
? (Deno.prompt("Your name", "stranger") ?? "stranger")
: (new TextDecoder().decode(await readAllStdin()).trim() || "stranger"); Defensive patterns
Strategy: validation
Validate before calling
async function promptSafe(message: string, def: string): Promise<string> {
if (!Deno.stdin.isTerminal()) return def;
try {
return Deno.prompt(message, def) ?? def;
} catch {
return def; // note: a native panic is NOT catchable; prefer flag/env input in exotic environments
}
} Type guard
const canPromptInteractively = () => Deno.stdin.isTerminal() && Deno.env.get("TERM") !== undefined; Prevention
- In containers/CI, prefer flags or env vars over Deno.prompt(message, default).
- Ensure a real controlling terminal and a valid TERM before offering the editable-default prompt.
- Offer a non-default Deno.prompt(message) path (different code path, no rustyline) as a fallback.
When it happens
Trigger: Calling `Deno.prompt("Name", "stranger")` (any prompt with a default) in an environment where stdin looks like a terminal but rustyline cannot init: minimal containers with a pty but missing /dev/tty, daemons/cron with a pseudo-terminal, some CI harnesses, or Windows console redirection edge cases. Deno.prompt without a default does not use this op.
Common situations: Interactive CLI tools exercised inside docker exec / debug-sidecar PTYs where /dev/tty is absent; scripts run from IDE test runners that allocate a pipe-like pty; SSH sessions with broken TERM values; automation that pipes input while a wrapper fakes a TTY (node-pty style) without a controlling terminal.
Related errors
AI-assisted analysis of denoland/deno@9ad36f7a2c (2026-08-20).
Data as JSON: /api/errors/ac99a44d170ad77b.
Report an issue: GitHub.