sinelaw/fresh · error

Failed to dup stdin

Error message

Failed to dup stdin: {}

What it means

At startup the editor duplicates the raw stdin file descriptor with libc::dup so a File can own a private handle to the piped input while the original fd is reused for the TTY. If dup fails (returns -1), the OS error is surfaced via this bail.

Solutions

  1. Run the editor with a valid stdin (a terminal or a pipe), e.g. don't close fd 0.
  2. Check the fd limit (ulimit -n) and raise it if dup failed with EMFILE.
  3. Inspect io::Error::last_os_error() in the message for the specific errno (EBADF/EMFILE/EPERM) and fix the environment accordingly.

Example fix

// before
$ fresh 0<&-            # stdin closed -> EBADF
// after
$ fresh < /dev/null      # provide a valid stdin
Defensive patterns

Strategy: validation

Validate before calling

// ensure fd 0 is open and usable before launching
// shell: [ -e /dev/fd/0 ] && exec fresh
// rust: assert!(libc::fcntl(0, libc::F_GETFD) != -1, "stdin closed");

Type guard

fn stdin_is_open() -> bool { unsafe { libc::fcntl(0, libc::F_GETFD) != -1 } }

Try / catch

match dup_stdin() { Err(e) if e.to_string().contains("Failed to dup stdin") => eprintln!("run with a valid stdin: fresh < /dev/null"), r => r }

Prevention

When it happens

Trigger: Launching the editor with stdin not being a valid open descriptor, running with a closed stdin (fd 0 closed), hitting the process fd limit, or operating in an environment where fd duplication is denied (e.g. restricted sandbox/seccomp).

Common situations: Running the binary with stdin explicitly closed (myapp 0<&-); spawning under a service manager or sandbox that closes/limits fds; ulimit -n exhausted so dup fails with EMFILE.

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


AI-assisted analysis of sinelaw/fresh@67894ca546 (2026-09-13). Data as JSON: /api/errors/cb22c972496b3275. Report an issue: GitHub.

Appendix: source

Thrown at crates/fresh-editor/src/main.rs:730

    Ok(StdinStreamState {
        spool,
        thread_handle: Some(thread_handle),
    })
}

/// Hand back the piped stdin as an owned reader, so it survives stdin being
/// reopened as the terminal.
#[cfg(unix)]
fn take_stdin_pipe() -> AnyhowResult<std::fs::File> {
    use std::os::unix::io::{AsRawFd, FromRawFd};

    let stdin_fd = io::stdin().as_raw_fd();
    // SAFETY: `dup` returns a fresh descriptor for the same pipe, which the
    // `File` below then owns; the original fd is left for `reopen_stdin_from_tty`.
    let pipe_fd = unsafe { libc::dup(stdin_fd) };
    if pipe_fd == -1 {
        anyhow::bail!("Failed to dup stdin: {}", io::Error::last_os_error());
    }
    // SAFETY: `pipe_fd` is a valid descriptor this function just created and
    // hands sole ownership of to the returned `File`.
    Ok(unsafe { std::fs::File::from_raw_fd(pipe_fd) })
}

/// Windows counterpart of [`take_stdin_pipe`].
#[cfg(windows)]
fn take_stdin_pipe() -> AnyhowResult<std::fs::File> {
    use std::os::windows::io::FromRawHandle;
    use windows_sys::Win32::Foundation::{
        DuplicateHandle, DUPLICATE_SAME_ACCESS, HANDLE, INVALID_HANDLE_VALUE,
    };
    use windows_sys::Win32::System::Console::{GetStdHandle, STD_INPUT_HANDLE};
    use windows_sys::Win32::System::Threading::GetCurrentProcess;

    // SAFETY: plain console/handle queries; every failure is checked below.
    let stdin_handle = unsafe { GetStdHandle(STD_INPUT_HANDLE) };

View on GitHub (pinned to 67894ca546)