can1357/oh-my-pi · error · BufReadDecoderError

underlying bytestream error: {}

Error message

underlying bytestream error: {}

What it means

BufReadDecoderError::Io wraps an io::Error from the underlying byte stream while the decoder was pulling more bytes. The `{}` display delegates to the inner io::Error, and the variant is marked #[source] so the root cause is preserved for error chains.

Source

Thrown at crates/pi-builtins/src/wc.rs:396

		
		/// Wraps a `std::io::BufRead` buffered byte stream and decode it as UTF-8.
		pub struct BufReadDecoder<B: BufRead> {
			buf_read:       B,
			bytes_consumed: usize,
			incomplete:     Incomplete,
		}
		
		#[derive(Debug, Error)]
		pub enum BufReadDecoderError<'a> {
			/// Represents one UTF-8 error in the byte stream.
			///
			/// In lossy decoding, each such error should be replaced with U+FFFD.
			/// (See `BufReadDecoder::next_lossy` and `BufReadDecoderError::lossy`.)
			#[error("invalid byte sequence: {:02x?}", .0)]
			InvalidByteSequence(&'a [u8]),
		
			/// An I/O error from the underlying byte stream
			#[error("underlying bytestream error: {}", .0)]
			Io(#[source] io::Error),
		}
		
		impl<B: BufRead> BufReadDecoder<B> {
			pub fn new(buf_read: B) -> Self {
				Self { buf_read, bytes_consumed: 0, incomplete: Incomplete::empty() }
			}
		
			/// Decode and consume the next chunk of UTF-8 input.
			///
			/// This method is intended to be called repeatedly until it returns `None`,
			/// which represents EOF from the underlying byte stream.
			/// This is similar to `Iterator::next`,
			/// except that decoded chunks borrow the decoder (~iterator)
			/// so they need to be handled or copied before the next chunk can start
			/// decoding.
			#[allow(clippy::cognitive_complexity)]
			pub fn next_strict(&mut self) -> Option<Result<&str, BufReadDecoderError<'_>>> {

View on GitHub (pinned to 9690622007)

Solutions

  1. Check the inner io::Error (via .source() or the printed message) for the real cause (errno)
  2. Verify the input is a readable regular file, not a directory or unreadable path
  3. If reading from a pipe, ensure the producer process stays alive and writes valid data
  4. Retry transient I/O (network mounts) or copy the file locally first

Example fix

// before: wc on a directory -> Io(os error 21)
// after
let path = "target";
let meta = std::fs::metadata(path)?;
if meta.is_dir() {
    eprintln!("skipping directory: {path}");
} else {
    // proceed with wc on the file
}
Defensive patterns

Strategy: retry

Validate before calling

fn validate_readable(path: &std::path::Path) -> Result<(), std::io::Error> {
    std::fs::File::open(path).map(|_| ())
}

Try / catch

match result {
    Err(BufReadDecoderError::Io(e)) if e.kind() == std::io::ErrorKind::PermissionDenied => {
        eprintln!("permission denied; skipping");
    }
    Err(BufReadDecoderError::Io(e)) if e.raw_os_error() == Some(21) => {
        eprintln!("input is a directory; skipping");
    }
    Err(BufReadDecoderError::Io(e)) => {
        // transient (EIO/ETIMEDOUT): retry after backoff
    }
    other => other.map(|_| ()),
}

Prevention

When it happens

Trigger: Any read failure on the underlying BufRead during wc's incremental decoding: EACCES/EPERM mid-read, EISDIR when reading a directory, device errors, or a broken pipe on stdin.

Common situations: wc pointed at a directory instead of a file; permission changed while reading; reading from a failing disk or a closed pipe (`prog | wc` where prog crashed); NFS timeouts.

Related errors


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