{"record":{"id":"320e2739430b149c","repo":"sinelaw/fresh","slug":"io-error-last-os-error","errorCode":null,"errorMessage":"io::Error::last_os_error()","messagePattern":"io::Error::last_os_error\\(\\)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/fresh-editor/src/main.rs","lineNumber":802,"sourceCode":"}\n\n/// Reopen stdin from /dev/tty after reading piped content.\n/// This allows crossterm to use the terminal for keyboard input\n/// even though the original stdin was a pipe.\n#[cfg(unix)]\nfn reopen_stdin_from_tty() -> AnyhowResult<()> {\n    use std::fs::File;\n    use std::os::unix::io::AsRawFd;\n\n    // Open /dev/tty - the controlling terminal\n    let tty = File::open(\"/dev/tty\")?;\n\n    // Duplicate /dev/tty to stdin (fd 0) using libc\n    // SAFETY: dup2 is safe to call with valid file descriptors\n    let result = unsafe { libc::dup2(tty.as_raw_fd(), libc::STDIN_FILENO) };\n\n    if result == -1 {\n        anyhow::bail!(io::Error::last_os_error());\n    }\n\n    Ok(())\n}\n\n/// Reopen stdin from CONIN$ on Windows.\n/// This allows crossterm to receive keyboard events after stdin was a pipe.\n#[cfg(windows)]\nfn reopen_stdin_from_tty() -> AnyhowResult<()> {\n    use windows_sys::Win32::Foundation::INVALID_HANDLE_VALUE;\n    use windows_sys::Win32::Storage::FileSystem::{\n        CreateFileW, FILE_GENERIC_READ, FILE_SHARE_READ, OPEN_EXISTING,\n    };\n    use windows_sys::Win32::System::Console::{SetStdHandle, STD_INPUT_HANDLE};\n\n    // \"CONIN$\" is the console input device on Windows\n    // This is analogous to /dev/tty on Unix\n    let conin: Vec<u16> = \"CONIN$\\0\".encode_utf16().collect();","sourceCodeStart":784,"sourceCodeEnd":820,"githubUrl":"https://github.com/sinelaw/fresh/blob/67894ca5463dbd7a89bb31add4627c27d6b79d83/crates/fresh-editor/src/main.rs#L784-L820","documentation":"On Unix, the editor duplicates the /dev/tty file descriptor onto stdin (fd 0) with libc::dup2. When dup2 returns -1 the raw OS errno is surfaced as an io::Error and bailed. This restores interactive terminal input after stdin was redirected or consumed (e.g. piped input).","triggerScenarios":"libc::dup2(tty_fd, STDIN_FILENO) returns -1: the tty fd is invalid/closed, the process has no controlling terminal, or fd 0 is in a state dup2 cannot replace.","commonSituations":"Running the editor without a controlling terminal (daemon, CI job, nohup with no tty); /dev/tty open failed earlier producing a bad fd; fd exhaustion (EMFILE-adjacent scenarios).","solutions":["Ensure the process has a controlling terminal (run from an interactive shell, not a daemon/CI context)","Check that the /dev/tty open (which produced `tty`) succeeded before dup2","Close leaked descriptors or raise the fd limit if file descriptors are exhausted","If no tty exists, skip the reopen and use stdin as-is or exit with a clear message"],"exampleFix":"// before\nlet result = unsafe { libc::dup2(tty.as_raw_fd(), libc::STDIN_FILENO) };\nif result == -1 { anyhow::bail!(io::Error::last_os_error()); }\n// after\nif !has_controlling_terminal() {\n    eprintln!(\"No controlling terminal; skipping stdin reopen\");\n    return Ok(());\n}\nlet result = unsafe { libc::dup2(tty.as_raw_fd(), libc::STDIN_FILENO) };\nif result == -1 { anyhow::bail!(io::Error::last_os_error()); }","handlingStrategy":"validation","validationCode":"fn has_controlling_terminal() -> bool {\n    std::path::Path::new(\"/dev/tty\").exists() && unsafe { libc::isatty(libc::STDERR_FILENO) } == 1\n}","typeGuard":"fn tty_opened(tty: &std::fs::File) -> bool { tty.as_raw_fd() >= 0 }","tryCatchPattern":"if let Err(e) = reopen_stdin_from_tty() {\n    eprintln!(\"stdin reopen failed: {e}\");\n}","preventionTips":["Only invoke the editor from an interactive terminal session","Check isatty on stderr/sid before attempting tty operations","Avoid nohup/daemon/CI contexts for interactive editor runs"],"tags":["unix","tty","dup2","stdin"],"backgroundTag":"file-open-failed","analyzedSha":"67894ca5463dbd7a89bb31add4627c27d6b79d83","analyzedAt":"2026-09-13T15:04:03.701Z","contentChangedAt":"2026-09-13T15:04:03.701Z","schemaVersion":2},"datasetVersion":"2026-09-16T09:17:16.951Z"}