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
- Provide explicit Stdio::piped() or an inherited handle instead of Stdio::null() for the failing stream.
- Check the embedded inner error ({e}) - if it indicates handle exhaustion, reduce open handles or raise the process quota.
- Run on a normal Windows installation or relax the sandbox that blocks the \\.\NUL device path.
- 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
- Prefer explicit Stdio::piped() or inherited handles when NUL may be restricted.
- Check handle counts if the inner error suggests exhaustion.
- Avoid sandbox/AV configurations that block the \\.\NUL device path.
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
- pidfd_spawnp succeeded but the child's PID could not be obta
- process groups are not supported on espidf
- creating or truncating a file requires write or append acces
- must specify at least one of read, write, or append access
- Path already exists
AI-assisted analysis of rust-lang/rust@7088e4b63a (2026-08-10).
Data as JSON: /api/errors/c40bb282ed7cbabd.
Report an issue: GitHub.