{"record":{"id":"e37ca4e7775dbd75","repo":"sigoden/aichat","slug":"interrupted","errorCode":null,"errorMessage":"Interrupted","messagePattern":"Interrupted","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"warning","filePath":"src/utils/input.rs","lineNumber":21,"sourceCode":"use crossterm::terminal::{disable_raw_mode, enable_raw_mode};\nuse std::io::{stdout, Write};\n\n/// Reads a single character from stdin without requiring Enter\n/// Returns the character if it's one of the valid options, or the default if Enter is pressed\npub fn read_single_key(valid_chars: &[char], default: char, prompt: &str) -> Result<char> {\n    print!(\"{prompt}\");\n    stdout().flush()?;\n\n    enable_raw_mode()?;\n\n    let result = loop {\n        if let Ok(Event::Key(KeyEvent {\n            code, modifiers, ..\n        })) = event::read()\n        {\n            match code {\n                KeyCode::Char('c') if modifiers.contains(KeyModifiers::CONTROL) => {\n                    break Err(anyhow::anyhow!(\"Interrupted\"));\n                }\n                KeyCode::Char(c) => {\n                    if valid_chars.contains(&c) {\n                        break Ok(c);\n                    }\n                    // Invalid character, continue loop\n                }\n                KeyCode::Enter => {\n                    break Ok(default);\n                }\n                _ => {\n                    // Other keys are ignored, continue loop\n                }\n            }\n        }\n    };\n\n    disable_raw_mode()?;","sourceCodeStart":3,"sourceCodeEnd":39,"githubUrl":"https://github.com/sigoden/aichat/blob/82976d349ad97ac9aae0655ad631dace5e2a6385/src/utils/input.rs#L3-L39","documentation":"`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.","triggerScenarios":"User presses Ctrl+C while the library is blocked in `read_single_key` waiting for a valid character in an interactive prompt.","commonSituations":"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.","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."],"exampleFix":"// before\nlet key = read_single_key(&valid_chars).context(\"Failed to read key\")?;\n// after\nmatch read_single_key(&valid_chars) {\n    Ok(k) => k,\n    Err(e) if e.to_string() == \"Interrupted\" => {\n        disable_raw_mode()?;\n        println!(\"\\nCancelled.\");\n        std::process::exit(130);\n    }\n    Err(e) => return Err(e.into()),\n}","handlingStrategy":"try-catch","validationCode":null,"typeGuard":"fn is_user_interrupt(err: &anyhow::Error) -> bool {\n    err.to_string() == \"Interrupted\"\n}","tryCatchPattern":"match read_single_key(&valid) {\n    Ok(k) => handle(k),\n    Err(e) if is_user_interrupt(&e) => {\n        let _ = crossterm::terminal::disable_raw_mode();\n        println!(\"\\nCancelled by user\");\n        std::process::exit(130); // conventional SIGINT exit code\n    }\n    Err(e) => return Err(e.into()),\n}","preventionTips":["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."],"tags":["terminal","interactive","user-input","cancellation"],"backgroundTag":"user-interrupt","analyzedSha":"82976d349ad97ac9aae0655ad631dace5e2a6385","analyzedAt":"2026-09-09T18:33:06.139Z","contentChangedAt":"2026-09-09T18:33:06.139Z","schemaVersion":2},"datasetVersion":"2026-09-16T09:17:16.951Z"}