sinelaw/fresh · error
stdin spool: read past the end of the drained region
Error message
stdin spool: read past the end of the drained region
What it means
The stdin spool backs a virtual file (piped stdin) and, once drained, only serves the bytes that were actually read. `read_range` requests `len` bytes at `offset`, and if the spool returns fewer bytes than requested it deliberately fails with `UnexpectedEof` instead of silently truncating, mirroring `File::read_exact` semantics on the local filesystem.
Solutions
- Clamp the requested range to the spool's actual current length before calling read_at/read_range
- Treat UnexpectedEof as end-of-file: shorten the read or stop reading
- Refresh the buffer's known length from the spool after stdin signals EOF rather than caching an older length
- If stdin is expected to deliver more data later, wait for the writer to finish instead of reading ahead
Example fix
// before let data = spool.read_range(path, offset, len)?; // after let available = spool.len(path).saturating_sub(offset); let len = len.min(available); let data = spool.read_range(path, offset, len)?;
Defensive patterns
Strategy: validation
Validate before calling
let spool_len = spool.len(path)?;
if offset + len > spool_len { len = spool_len - offset; } // clamp before reading Try / catch
match spool.read_range(path, offset, len) {
Ok(data) => { /* use data */ }
Err(e) if e.kind() == io::ErrorKind::UnexpectedEof => { /* treat as EOF */ }
Err(e) => return Err(e),
} Prevention
- Query the spool length before computing read ranges
- Never cache stdin length across EOF transitions
- Map UnexpectedEof to an end-of-file condition rather than a hard failure
- Clamp reads to [0, len) of the drained region
When it happens
Trigger: Calling `read_range` (via the stdin spool's read path) with an offset+len that extends beyond the end of the already-drained stdin data — e.g. reading with stale length metadata, or requesting more bytes than stdin actually provided.
Common situations: Reading a virtual stdin file after new stdin data has fully drained; a buffer/view holding an outdated byte-length and refreshing a region at EOF; tools diffing or tailing a stdin-backed buffer past its end.
Understand the failure class
Background: "failed to read file", EACCES, ENOENT and "could not read <path>" errors: when a program can't read a file from disk — this error's family across 49 libraries.
Related errors
- Failed to get stdin handle
- Failed to duplicate stdin handle
- io::Error::last_os_error()
- Failed to set stdin to CONIN$
- No data piped to stdin
AI-assisted analysis of sinelaw/fresh@67894ca546 (2026-09-13).
Data as JSON: /api/errors/e51e6b8e0455d048.
Report an issue: GitHub.
Appendix: source
Thrown at crates/fresh-editor/src/services/stdin_spool.rs:154
/// is the part worth seeing in a log line anyway.
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("SpoolFileSystem")
.field("spool", &self.spool.path)
.finish_non_exhaustive()
}
}
impl FileSystem for SpoolFileSystem {
// --- the two the spool actually answers ------------------------------
fn read_range(&self, path: &Path, offset: u64, len: usize) -> io::Result<Vec<u8>> {
if self.spool.owns(path) {
let data = self.spool.read_at(offset, len)?;
if data.len() < len {
// Matches `File::read_exact`, which is what the local
// filesystem uses: asking for a range past the end is an
// error rather than a silent truncation.
return Err(io::Error::new(
io::ErrorKind::UnexpectedEof,
"stdin spool: read past the end of the drained region",
));
}
return Ok(data);
}
self.inner.read_range(path, offset, len)
}
fn metadata(&self, path: &Path) -> io::Result<FileMetadata> {
if self.spool.owns(path) {
return Ok(FileMetadata::new(self.spool.len()?));
}
self.inner.metadata(path)
}
fn read_file(&self, path: &Path) -> io::Result<Vec<u8>> {
if self.spool.owns(path) {View on GitHub (pinned to 67894ca546)