sinelaw/fresh · error
io::Error::last_os_error()
Error message
io::Error::last_os_error()
What it means
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).
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
Example fix
// before
let result = unsafe { libc::dup2(tty.as_raw_fd(), libc::STDIN_FILENO) };
if result == -1 { anyhow::bail!(io::Error::last_os_error()); }
// after
if !has_controlling_terminal() {
eprintln!("No controlling terminal; skipping stdin reopen");
return Ok(());
}
let result = unsafe { libc::dup2(tty.as_raw_fd(), libc::STDIN_FILENO) };
if result == -1 { anyhow::bail!(io::Error::last_os_error()); } Defensive patterns
Strategy: validation
Validate before calling
fn has_controlling_terminal() -> bool {
std::path::Path::new("/dev/tty").exists() && unsafe { libc::isatty(libc::STDERR_FILENO) } == 1
} Type guard
fn tty_opened(tty: &std::fs::File) -> bool { tty.as_raw_fd() >= 0 } Try / catch
if let Err(e) = reopen_stdin_from_tty() {
eprintln!("stdin reopen failed: {e}");
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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).
Understand the failure class
Background: "open() failed", "failed to open file", "cannot create file" — what a file open error means and how to fix it — this error's family across 42 libraries.
Related errors
- Failed to dup stdin
- Failed to get stdin handle
- Failed to duplicate stdin handle
- Failed to set stdin to CONIN$
- No data piped to stdin
AI-assisted analysis of sinelaw/fresh@67894ca546 (2026-09-13).
Data as JSON: /api/errors/320e2739430b149c.
Report an issue: GitHub.
Appendix: source
Thrown at crates/fresh-editor/src/main.rs:802
}
/// Reopen stdin from /dev/tty after reading piped content.
/// This allows crossterm to use the terminal for keyboard input
/// even though the original stdin was a pipe.
#[cfg(unix)]
fn reopen_stdin_from_tty() -> AnyhowResult<()> {
use std::fs::File;
use std::os::unix::io::AsRawFd;
// Open /dev/tty - the controlling terminal
let tty = File::open("/dev/tty")?;
// Duplicate /dev/tty to stdin (fd 0) using libc
// SAFETY: dup2 is safe to call with valid file descriptors
let result = unsafe { libc::dup2(tty.as_raw_fd(), libc::STDIN_FILENO) };
if result == -1 {
anyhow::bail!(io::Error::last_os_error());
}
Ok(())
}
/// Reopen stdin from CONIN$ on Windows.
/// This allows crossterm to receive keyboard events after stdin was a pipe.
#[cfg(windows)]
fn reopen_stdin_from_tty() -> AnyhowResult<()> {
use windows_sys::Win32::Foundation::INVALID_HANDLE_VALUE;
use windows_sys::Win32::Storage::FileSystem::{
CreateFileW, FILE_GENERIC_READ, FILE_SHARE_READ, OPEN_EXISTING,
};
use windows_sys::Win32::System::Console::{SetStdHandle, STD_INPUT_HANDLE};
// "CONIN$" is the console input device on Windows
// This is analogous to /dev/tty on Unix
let conin: Vec<u16> = "CONIN$\0".encode_utf16().collect();View on GitHub (pinned to 67894ca546)