can1357/oh-my-pi · error · io::Error

error writing 'standard output

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.

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.

Appendix: 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)