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

  1. Treat this error as an expected user cancellation: catch it, restore terminal state, and exit with a non-error status.
  2. Disable raw mode / clean up the terminal in a Drop guard or on this specific error so the prompt doesn't corrupt the shell.
  3. If interruption is unacceptable, prompt again instead of propagating the error.
  4. 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

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


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)