sigoden/aichat · warning · anyhow::Error
Interrupted
Error message
Interrupted
What it means
`read_single_key` reads key events from the terminal; when the user presses Ctrl+C (KeyCode::Char('c') with KeyModifiers::CONTROL), it exits its loop by returning `Err(anyhow!("Interrupted"))`. This is a deliberate user-abort signal from an interactive prompt, raised so callers like `shell_execute` can unwind cleanly.
Solutions
- Treat this error as an expected user cancellation: catch it, restore terminal state, and exit with a non-error status.
- Disable raw mode / clean up the terminal in a Drop guard or on this specific error so the prompt doesn't corrupt the shell.
- If interruption is unacceptable, prompt again instead of propagating the error.
- Distinguish this error from real failures by matching on its message or introducing a dedicated cancellation type.
Example fix
// before
let key = read_single_key(&valid_chars).context("Failed to read key")?;
// after
match read_single_key(&valid_chars) {
Ok(k) => k,
Err(e) if e.to_string() == "Interrupted" => {
disable_raw_mode()?;
println!("\nCancelled.");
std::process::exit(130);
}
Err(e) => return Err(e.into()),
} Defensive patterns
Strategy: try-catch
Type guard
fn is_user_interrupt(err: &anyhow::Error) -> bool {
err.to_string() == "Interrupted"
} Try / catch
match read_single_key(&valid) {
Ok(k) => handle(k),
Err(e) if is_user_interrupt(&e) => {
let _ = crossterm::terminal::disable_raw_mode();
println!("\nCancelled by user");
std::process::exit(130); // conventional SIGINT exit code
}
Err(e) => return Err(e.into()),
} Prevention
- Always restore terminal state (disable_raw_mode) when an interactive prompt unwinds.
- Exit with status 130 on Ctrl+C so scripts treat it as an intentional abort.
- Use a dedicated cancellation error type instead of string matching if you control the code.
- Warn users before long-running prompts that Ctrl+C will cancel.
When it happens
Trigger: User presses Ctrl+C while the library is blocked in `read_single_key` waiting for a valid character in an interactive prompt.
Common situations: User changes their mind during an interactive confirmation; automation wrapping the CLI sends a SIGINT-equivalent key sequence; accidental Ctrl+C while the prompt is focused.
Related errors
- Failed to init rag in non-interactive mode
- Aborted!
- Aborted.
- Invalid wrap value
- Failed to send OSC52 sequence
AI-assisted analysis of sigoden/aichat@82976d349a (2026-09-09).
Data as JSON: /api/errors/e37ca4e7775dbd75.
Report an issue: GitHub.
Appendix: source
Thrown at src/utils/input.rs:21
use crossterm::terminal::{disable_raw_mode, enable_raw_mode};
use std::io::{stdout, Write};
/// Reads a single character from stdin without requiring Enter
/// Returns the character if it's one of the valid options, or the default if Enter is pressed
pub fn read_single_key(valid_chars: &[char], default: char, prompt: &str) -> Result<char> {
print!("{prompt}");
stdout().flush()?;
enable_raw_mode()?;
let result = loop {
if let Ok(Event::Key(KeyEvent {
code, modifiers, ..
})) = event::read()
{
match code {
KeyCode::Char('c') if modifiers.contains(KeyModifiers::CONTROL) => {
break Err(anyhow::anyhow!("Interrupted"));
}
KeyCode::Char(c) => {
if valid_chars.contains(&c) {
break Ok(c);
}
// Invalid character, continue loop
}
KeyCode::Enter => {
break Ok(default);
}
_ => {
// Other keys are ignored, continue loop
}
}
}
};
disable_raw_mode()?;View on GitHub (pinned to 82976d349a)