ultraworkers/claw-code · error

rustyline editor should initialize

Error message

rustyline editor should initialize

What it means

LineEditor::new in the claw CLI (rust/crates/rusty-claude-cli/src/input.rs:114) calls Editor::with_config(config).expect("rustyline editor should initialize"). rustyline's Editor construction opens and configures the terminal (termios on Unix, console mode on Windows); when that fails it returns a ReadlineError (typically Io or Errno), and this .expect turns it into a panic during REPL startup. The usual cause is that stdin/stdout is not an interactive terminal or the terminal cannot be configured (TERM unset/dumb, no /dev/tty).

Source

Thrown at rust/crates/rusty-claude-cli/src/input.rs:114

}

impl Validator for SlashCommandHelper {}
impl Helper for SlashCommandHelper {}

pub struct LineEditor {
    prompt: String,
    editor: Editor<SlashCommandHelper, DefaultHistory>,
}

impl LineEditor {
    #[must_use]
    pub fn new(prompt: impl Into<String>, completions: Vec<String>) -> Self {
        let config = Config::builder()
            .completion_type(CompletionType::List)
            .edit_mode(EditMode::Emacs)
            .build();
        let mut editor = Editor::<SlashCommandHelper, DefaultHistory>::with_config(config)
            .expect("rustyline editor should initialize");
        editor.set_helper(Some(SlashCommandHelper::new(completions)));
        editor.bind_sequence(KeyEvent(KeyCode::Char('J'), Modifiers::CTRL), Cmd::Newline);
        editor.bind_sequence(KeyEvent(KeyCode::Enter, Modifiers::SHIFT), Cmd::Newline);

        Self {
            prompt: prompt.into(),
            editor,
        }
    }

    pub fn push_history(&mut self, entry: impl Into<String>) {
        let entry = entry.into();
        if entry.trim().is_empty() {
            return;
        }

        let _ = self.editor.add_history_entry(entry);
    }

View on GitHub (pinned to 08106b0c37)

Solutions

  1. Run the REPL from an actual interactive terminal (attach a pty: `docker exec -it`, `ssh -t`, a real terminal emulator).
  2. Use the CLI's non-interactive mode instead of the REPL — pass the prompt as an argument / print mode rather than piping stdin, so LineEditor is never constructed.
  3. Set a valid TERM (e.g. `export TERM=xterm-256color`) when TERM is unset or 'dumb'.
  4. If you embed rustyline yourself, don't .expect(): match the Err(ReadlineError) and fall back to plain stdin line reading.
  5. Verify with a tty check (`[ -t 0 ]` / std::io::IsTerminal) before launching the interactive loop and fall back to non-interactive input.

Example fix

// before (input.rs)
let mut editor = Editor::<SlashCommandHelper, DefaultHistory>::with_config(config)
    .expect("rustyline editor should initialize");

// after: fall back to plain stdin when the terminal cannot be initialized
let editor = match Editor::<SlashCommandHelper, DefaultHistory>::with_config(config) {
    Ok(mut editor) => {
        editor.set_helper(Some(SlashCommandHelper::new(completions)));
        LineEditorKind::Rustyline(editor)
    }
    Err(err) => {
        eprintln!("warning: interactive editor unavailable ({err}); using plain input");
        LineEditorKind::Plain
    }
};
Defensive patterns

Strategy: validation

Validate before calling

// before starting the interactive REPL, verify a usable terminal
use std::io::IsTerminal;

fn can_run_repl() -> bool {
    std::io::stdin().is_terminal()
        && std::io::stdout().is_terminal()
        && std::env::var("TERM").map(|t| !t.is_empty() && t != "dumb").unwrap_or(false)
}

if !can_run_repl() {
    eprintln!("stdin/stdout is not an interactive terminal; use non-interactive mode");
    std::process::exit(2);
}

Try / catch

// if you embed rustyline directly, avoid .expect() on Editor creation
match Editor::<H, DefaultHistory>::with_config(config) {
    Ok(editor) => run_repl(editor),
    Err(rustyline::error::ReadlineError::Io(e)) => fall_back_to_plain_stdin(e),
    Err(rustyline::error::ReadlineError::Errno(e)) => fall_back_to_plain_stdin(e),
    Err(other) => return Err(other.into()),
}

Prevention

When it happens

Trigger: Starting the claw interactive REPL when stdin is not a TTY — e.g. `cat file | claw`, `claw < input.txt`, running under CI/ssh without a pty, or inside `docker run` without `-t`. Also triggered by TERM=dumb or an unset TERM on Unix, or when /dev/tty cannot be opened, since rustyline must put the terminal into raw mode at Editor creation.

Common situations: Piping input into the CLI in scripts or CI and accidentally entering REPL mode; docker/Kubernetes containers without TTY allocation; cron jobs or service wrappers invoking the binary; broken TERM after su/sudo or in minimal distro images; Windows consoles with unavailable console APIs.

Related errors


AI-assisted analysis of ultraworkers/claw-code@08106b0c37 (2026-08-18). Data as JSON: /api/errors/401584919dfd279a. Report an issue: GitHub.