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

  1. Ensure the downstream consumer of the pipe stays open long enough, or restructure the pipeline
  2. Check available disk space (df -h) when output is redirected to a file
  3. Inspect the wrapped inner err for the OS-level cause (EPIPE vs ENOSPC vs EIO)
  4. 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

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


AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31). Data as JSON: /api/errors/1286f47cca7208aa. Report an issue: GitHub.