From c2ca0a7813cdbcd27bb3b76649102dad72e87ef8 Mon Sep 17 00:00:00 2001 From: Emir Esenov <110808673+emiresenov@users.noreply.github.com> Date: Mon, 23 Feb 2026 18:14:58 +0100 Subject: [PATCH 1/2] fix: make readline history loading non-blocking Use thread spawn to load history file content and populate readline history line-by-line instead of using load_history() which can block indefinitely on corrupted files. Fixes startup hang while preserving command history functionality. --- crates/chat-cli/src/cli/chat/prompt.rs | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/crates/chat-cli/src/cli/chat/prompt.rs b/crates/chat-cli/src/cli/chat/prompt.rs index e58879c930..f2e7afc8f2 100644 --- a/crates/chat-cli/src/cli/chat/prompt.rs +++ b/crates/chat-cli/src/cli/chat/prompt.rs @@ -587,10 +587,27 @@ pub fn rl( rl.set_helper(Some(h)); // Load history from CLI bash history file - if let Err(e) = rl.load_history(&rl.helper().unwrap().get_history_path()) { - if !matches!(e, ReadlineError::Io(ref io_err) if io_err.kind() == std::io::ErrorKind::NotFound) { + // Use a separate thread to prevent indefinite blocking on corrupted files + let history_path = rl.helper().unwrap().get_history_path(); + let history_path_clone = history_path.clone(); + + let load_handle = std::thread::spawn(move || { + std::fs::read_to_string(&history_path_clone) + }); + + match load_handle.join() { + Ok(Ok(contents)) => { + for line in contents.lines() { + let _ = rl.add_history_entry(line); + } + } + Ok(Err(e)) if e.kind() != std::io::ErrorKind::NotFound => { eprintln!("Warning: Failed to load history: {}", e); } + Err(_) => { + eprintln!("Warning: History loading failed unexpectedly"); + } + _ => {} // NotFound is expected on first run } // Add custom keybinding for Ctrl+D to open delegate command (configurable) From a97d3d2a548173a073c3ab91ec7f41ccaae1800d Mon Sep 17 00:00:00 2001 From: Emir Esenov <110808673+emiresenov@users.noreply.github.com> Date: Thu, 26 Feb 2026 15:47:33 +0100 Subject: [PATCH 2/2] (hot)fix: force immediate exit on /quit command to prevent hang The CLI was hanging after /quit because the code would attempt to read input again using rustyline's blocking readline() call before the graceful shutdown could complete. This is a pragmatic fix that ensures /quit exits immediately as users expect, though it skips graceful cleanup (Drop implementations, async task cleanup, telemetry). The OS handles resource cleanup on exit. A proper long-term solution would involve making readline non-blocking or restructuring the async/sync input handling, but that would require significant refactoring. --- crates/chat-cli/src/cli/chat/mod.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/crates/chat-cli/src/cli/chat/mod.rs b/crates/chat-cli/src/cli/chat/mod.rs index 099c0c8761..ebb1c2acab 100644 --- a/crates/chat-cli/src/cli/chat/mod.rs +++ b/crates/chat-cli/src/cli/chat/mod.rs @@ -2150,8 +2150,11 @@ impl ChatSession { ) .await; - if matches!(chat_state, ChatState::Exit) - || matches!(chat_state, ChatState::HandleResponseStream(_)) + if matches!(chat_state, ChatState::Exit) { + std::process::exit(0); + } + + if matches!(chat_state, ChatState::HandleResponseStream(_)) || matches!(chat_state, ChatState::HandleInput { input: _ }) // TODO(bskiser): this is just a hotfix for handling state changes // from manually running /compact, without impacting behavior of