{"record":{"id":"401584919dfd279a","repo":"ultraworkers/claw-code","slug":"rustyline-editor-should-initialize","errorCode":null,"errorMessage":"rustyline editor should initialize","messagePattern":"rustyline editor should initialize","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"rust/crates/rusty-claude-cli/src/input.rs","lineNumber":114,"sourceCode":"}\n\nimpl Validator for SlashCommandHelper {}\nimpl Helper for SlashCommandHelper {}\n\npub struct LineEditor {\n    prompt: String,\n    editor: Editor<SlashCommandHelper, DefaultHistory>,\n}\n\nimpl LineEditor {\n    #[must_use]\n    pub fn new(prompt: impl Into<String>, completions: Vec<String>) -> Self {\n        let config = Config::builder()\n            .completion_type(CompletionType::List)\n            .edit_mode(EditMode::Emacs)\n            .build();\n        let mut editor = Editor::<SlashCommandHelper, DefaultHistory>::with_config(config)\n            .expect(\"rustyline editor should initialize\");\n        editor.set_helper(Some(SlashCommandHelper::new(completions)));\n        editor.bind_sequence(KeyEvent(KeyCode::Char('J'), Modifiers::CTRL), Cmd::Newline);\n        editor.bind_sequence(KeyEvent(KeyCode::Enter, Modifiers::SHIFT), Cmd::Newline);\n\n        Self {\n            prompt: prompt.into(),\n            editor,\n        }\n    }\n\n    pub fn push_history(&mut self, entry: impl Into<String>) {\n        let entry = entry.into();\n        if entry.trim().is_empty() {\n            return;\n        }\n\n        let _ = self.editor.add_history_entry(entry);\n    }","sourceCodeStart":96,"sourceCodeEnd":132,"githubUrl":"https://github.com/ultraworkers/claw-code/blob/08106b0c3771ef5b4a5aa176acccd460e88b7325/rust/crates/rusty-claude-cli/src/input.rs#L96-L132","documentation":"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).","triggerScenarios":"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.","commonSituations":"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.","solutions":["Run the REPL from an actual interactive terminal (attach a pty: `docker exec -it`, `ssh -t`, a real terminal emulator).","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.","Set a valid TERM (e.g. `export TERM=xterm-256color`) when TERM is unset or 'dumb'.","If you embed rustyline yourself, don't .expect(): match the Err(ReadlineError) and fall back to plain stdin line reading.","Verify with a tty check (`[ -t 0 ]` / std::io::IsTerminal) before launching the interactive loop and fall back to non-interactive input."],"exampleFix":"// before (input.rs)\nlet mut editor = Editor::<SlashCommandHelper, DefaultHistory>::with_config(config)\n    .expect(\"rustyline editor should initialize\");\n\n// after: fall back to plain stdin when the terminal cannot be initialized\nlet editor = match Editor::<SlashCommandHelper, DefaultHistory>::with_config(config) {\n    Ok(mut editor) => {\n        editor.set_helper(Some(SlashCommandHelper::new(completions)));\n        LineEditorKind::Rustyline(editor)\n    }\n    Err(err) => {\n        eprintln!(\"warning: interactive editor unavailable ({err}); using plain input\");\n        LineEditorKind::Plain\n    }\n};","handlingStrategy":"validation","validationCode":"// before starting the interactive REPL, verify a usable terminal\nuse std::io::IsTerminal;\n\nfn can_run_repl() -> bool {\n    std::io::stdin().is_terminal()\n        && std::io::stdout().is_terminal()\n        && std::env::var(\"TERM\").map(|t| !t.is_empty() && t != \"dumb\").unwrap_or(false)\n}\n\nif !can_run_repl() {\n    eprintln!(\"stdin/stdout is not an interactive terminal; use non-interactive mode\");\n    std::process::exit(2);\n}","typeGuard":null,"tryCatchPattern":"// if you embed rustyline directly, avoid .expect() on Editor creation\nmatch Editor::<H, DefaultHistory>::with_config(config) {\n    Ok(editor) => run_repl(editor),\n    Err(rustyline::error::ReadlineError::Io(e)) => fall_back_to_plain_stdin(e),\n    Err(rustyline::error::ReadlineError::Errno(e)) => fall_back_to_plain_stdin(e),\n    Err(other) => return Err(other.into()),\n}","preventionTips":["Never pipe stdin into the REPL (`claw < file`); use the CLI's one-shot/print mode for scripted input.","Allocate a TTY in containers and remote sessions (`docker run -it`, `ssh -t`).","Ensure TERM is set to a real terminal type (xterm-256color) and never 'dumb' when launching interactively.","Gate REPL startup on `[ -t 0 ]` / std::io::IsTerminal checks and fall back to non-interactive input."],"tags":["rust","rustyline","terminal","repl","tty","claw","cli"],"backgroundTag":"unsupported-terminal","analyzedSha":"08106b0c3771ef5b4a5aa176acccd460e88b7325","analyzedAt":"2026-08-18T00:29:38.590Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}