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

  1. Run the script in a real interactive terminal (a proper login shell/SSH session with a controlling TTY).
  2. 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.
  3. Set a sane TERM (e.g. `TERM=xterm-256color`) in containerized/pty environments before running.
  4. 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

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.