can1357/oh-my-pi · error · io::Error
error writing 'standard output': {err}
Error message
error writing 'standard output': {err} What it means
When writing head's output to stdout fails, the io::Error is re-wrapped with the message "error writing 'standard output': {err}" preserving the original error kind. This mirrors GNU coreutils' phrasing and clearly attributes mid-stream write failures (broken pipe, ENOSPC, EIO) to stdout rather than input reading.
Source
Thrown at crates/pi-builtins/src/head.rs:1151
options.quiet = matches.get_flag(options::QUIET);
options.verbose = matches.get_flag(options::VERBOSE);
options.line_ending = LineEnding::from_zero_flag(matches.get_flag(options::ZERO));
options.presume_input_pipe = matches.get_flag(options::PRESUME_INPUT_PIPE);
options.mode = Mode::from(matches)?;
options.files = match matches.get_many::<OsString>(options::FILES) {
Some(v) => v.cloned().collect(),
None => vec![OsString::from("-")],
};
Ok(options)
}
}
#[inline]
fn wrap_in_stdout_error(err: io::Error) -> io::Error {
io::Error::new(err.kind(), format!("error writing 'standard output': {err}"))
}
fn read_n_bytes(input: impl Read, output: &mut impl Write, n: u64) -> io::Result<u64> {
let mut reader = input.take(n);
let bytes_written = io::copy(&mut reader, output).map_err(wrap_in_stdout_error)?;
output.flush().map_err(wrap_in_stdout_error)?;
Ok(bytes_written)
}
fn read_n_lines(
input: &mut impl io::BufRead,
output: &mut impl Write,
n: u64,
separator: u8,
) -> io::Result<u64> {
let mut reader = take_lines(input, n, separator);
let bytes_written = io::copy(&mut reader, output).map_err(wrap_in_stdout_error)?;View on GitHub (pinned to 9690622007)
Solutions
- Ensure the downstream consumer of the pipe stays open long enough, or restructure the pipeline
- Check available disk space (df -h) when output is redirected to a file
- Inspect the wrapped inner err for the OS-level cause (EPIPE vs ENOSPC vs EIO)
- In callers, match on the error kind to treat EPIPE as benign early-exit
Example fix
// before
let out = head_to_stdout(path, 10).unwrap(); // panics on EPIPE
// after
match head_to_stdout(path, 10) {
Ok(n) => /* ... */,
Err(e) if e.kind() == io::ErrorKind::BrokenPipe => return Ok(()),
Err(e) => return Err(e),
} Defensive patterns
Strategy: try-catch
Try / catch
match head_result {
Err(e) if e.kind() == io::ErrorKind::BrokenPipe => { /* consumer closed stdout; exit quietly */ }
Err(e) if e.to_string().starts_with("error writing 'standard output'") => {
eprintln!("stdout write failed: {e}");
}
Err(e) => return Err(e),
Ok(v) => /* ... */,
} Prevention
- Avoid pipelines where downstream consumers exit before upstream finishes; drain output if needed
- Monitor disk space in environments where head output is redirected to files
- Treat BrokenPipe as expected control flow in Unix pipelines, not a crash
When it happens
Trigger: Running the head builtin when stdout is a closed pipe (consumer exited, SIGPIPE/EPIPE), a full disk (ENOSPC) while redirecting, or an otherwise failing output descriptor during io::copy in read_n_bytes/read_n_lines.
Common situations: `head big.log | head -n 1` where the downstream consumer closes the pipe early; cron jobs with redirected stdout to a full filesystem; containers with closed/disconnected stdout.
Related errors
- error reading {}: {}
- 2
- failed to write to stdout: {}
- Computed edit range is out of bounds
- Too many levels of symbolic links
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/1286f47cca7208aa.
Report an issue: GitHub.