Skip to content

Command::spawn() does not clear stdout's buffer on child process #148278

@andreacorbellini

Description

@andreacorbellini

Command::spawn() doesn't do any cleanup on the stdout buffer. This is normally not a problem, because usually the buffer is discarded when the subcommand is executed, however this can result in duplicated output when printing from pre_exec(), as demonstrated by the following example:

I tried this code:

use std::os::unix::process::CommandExt;
use std::process::Command;

fn main() {
    print!("hello ");
    unsafe { 
        Command::new("cat")
            .arg("/dev/null")
            .pre_exec(|| {
                println!("from child");
                Ok(())
            })
            .spawn()
            .expect("spawn failed")
    };
    println!("from parent");
}

I would expect the output to be similar to this:

from child
hello from parent

but instead the actual output is this:

hello from child
hello from parent

"hello " is repeated twice because the stdout buffer is preserved when the process is forked.

On a related note, forgetting to flush in pre_exec can also lead to the data printed in the child process to be lost:

use std::os::unix::process::CommandExt;
use std::process::Command;

fn main() {
    unsafe {
        Command::new("cat")
            .arg("/dev/null")
            .pre_exec(|| {
                print!("hello");
                Ok(())
            })
            .spawn()
            .expect("spawn failed")
    };
}

The code above prints nothing, while the following does print hello as expected:

use std::io::Write;
use std::io::stdout;
use std::os::unix::process::CommandExt;
use std::process::Command;
    
fn main() {
    unsafe {
        Command::new("cat")
            .arg("/dev/null")
            .pre_exec(|| {
                print!("hello");
                stdout().lock().flush()
            })
            .spawn()
            .expect("spawn failed")
    };
}

Given the unsafe/precarious nature of pre_exec() one might argue that this second issue (not flushing before exec) may be acceptable, but it's undocumented and I think it's a bit unexpected because println and print lead to very different outcomes.

(For context, this was discovered while working on #148274)

Meta

rustc --version --verbose:

rustc 1.90.0 (1159e78c4 2025-09-14)
binary: rustc
commit-hash: 1159e78c4747b02ef996e55082b704c09b970588
commit-date: 2025-09-14
host: x86_64-unknown-linux-gnu
release: 1.90.0
LLVM version: 20.1.8

Metadata

Metadata

Assignees

No one assigned

    Labels

    C-bugCategory: This is a bug.needs-triageThis issue may need triage. Remove it if it has been sufficiently triaged.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions