rust-lang/rust · error · io::Error

failed to open NUL device for child {stream}: {e}

Error message

failed to open NUL device for child {stream}: {e}

What it means

Produced by Rust std's Windows process spawner when Stdio::Null is configured for a child stream and opening the NUL device (\\.\NUL) fails. std deliberately re-wraps the raw NotFound error to name which stream (stdin/stdout/stderr) could not be bound, because a bare NotFound here is easily misread as the spawned program being missing. The underlying error kind and message are preserved inside the new message.

Source

Thrown at library/std/src/sys/process/windows.rs:655

            // processes (as this is about to be inherited).
            Stdio::Null => {
                let mut opts = OpenOptions::new();
                opts.read(stdio_id == c::STD_INPUT_HANDLE);
                opts.write(stdio_id != c::STD_INPUT_HANDLE);
                opts.inherit_handle(true);
                File::open(Path::new(r"\\.\NUL"), &opts).map(|file| file.into_inner()).map_err(
                    |e| {
                        // A raw `NotFound` here is easily mistaken for the program
                        // being missing, so say what actually failed to open.
                        // `spawn` only passes the three standard ids, but print
                        // anything else as a number rather than mislabeling it.
                        let stream = match stdio_id {
                            c::STD_INPUT_HANDLE => "stdin".to_string(),
                            c::STD_OUTPUT_HANDLE => "stdout".to_string(),
                            c::STD_ERROR_HANDLE => "stderr".to_string(),
                            id => format!("stdio handle {id}"),
                        };
                        Error::new(
                            e.kind(),
                            format!("failed to open NUL device for child {stream}: {e}"),
                        )
                    },
                )
            }
        }
    }
}

impl From<ChildPipe> for Stdio {
    fn from(pipe: ChildPipe) -> Stdio {
        Stdio::Pipe(pipe)
    }
}

impl From<Handle> for Stdio {
    fn from(pipe: Handle) -> Stdio {

View on GitHub (pinned to 7088e4b63a)

Solutions

  1. Provide explicit Stdio::piped() or an inherited handle instead of Stdio::null() for the failing stream.
  2. Check the embedded inner error ({e}) - if it indicates handle exhaustion, reduce open handles or raise the process quota.
  3. Run on a normal Windows installation or relax the sandbox that blocks the \\.\NUL device path.
  4. Disable/reconfigure antivirus hooks that intercept device-path file opens.

Example fix

// before
let child = Command::new("tool")
    .stdout(Stdio::null())
    .stderr(Stdio::null())
    .spawn()?;

// after (pipe instead of NUL)
let child = Command::new("tool")
    .stdout(Stdio::piped())
    .stderr(Stdio::piped())
    .spawn()?;
Defensive patterns

Strategy: fallback

Validate before calling

fn pick_stdio() -> std::process::Stdio {
    // If NUL is known-restricted, pipe instead of null.
    if std::fs::File::open(r"\\.\NUL").is_err() {
        std::process::Stdio::piped()
    } else {
        std::process::Stdio::null()
    }
}

Try / catch

match Command::new(t).stdout(Stdio::null()).spawn() {
    Ok(c) => Ok(c),
    Err(e) if e.to_string().contains("failed to open NUL device") => {
        Command::new(t).stdout(Stdio::piped()).spawn()
    }
    Err(e) => Err(e),
}

Prevention

When it happens

Trigger: Command::spawn on Windows with stdin/stdout/stdout set to Stdio::null() (the default for unconfigured streams on some paths) when File::open("\\.\NUL", ...) fails - e.g. NUL device disabled/renamed, extreme handle exhaustion, antivirus intercepting device opens, or a locked-down environment blocking device namespace access.

Common situations: Sandboxed/CI runner that restricts the \\.\NUL device; broken Windows image or security product hooking CreateFileW on device paths; running under a context where the NUL device symlink is missing.

Related errors


AI-assisted analysis of rust-lang/rust@7088e4b63a (2026-08-10). Data as JSON: /api/errors/c40bb282ed7cbabd. Report an issue: GitHub.