{"record":{"id":"e51e6b8e0455d048","repo":"sinelaw/fresh","slug":"stdin-spool-read-past-the-end-of-the-drained-region","errorCode":null,"errorMessage":"stdin spool: read past the end of the drained region","messagePattern":"stdin spool: read past the end of the drained region","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/fresh-editor/src/services/stdin_spool.rs","lineNumber":154,"sourceCode":"    /// is the part worth seeing in a log line anyway.\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        f.debug_struct(\"SpoolFileSystem\")\n            .field(\"spool\", &self.spool.path)\n            .finish_non_exhaustive()\n    }\n}\n\nimpl FileSystem for SpoolFileSystem {\n    // --- the two the spool actually answers ------------------------------\n\n    fn read_range(&self, path: &Path, offset: u64, len: usize) -> io::Result<Vec<u8>> {\n        if self.spool.owns(path) {\n            let data = self.spool.read_at(offset, len)?;\n            if data.len() < len {\n                // Matches `File::read_exact`, which is what the local\n                // filesystem uses: asking for a range past the end is an\n                // error rather than a silent truncation.\n                return Err(io::Error::new(\n                    io::ErrorKind::UnexpectedEof,\n                    \"stdin spool: read past the end of the drained region\",\n                ));\n            }\n            return Ok(data);\n        }\n        self.inner.read_range(path, offset, len)\n    }\n\n    fn metadata(&self, path: &Path) -> io::Result<FileMetadata> {\n        if self.spool.owns(path) {\n            return Ok(FileMetadata::new(self.spool.len()?));\n        }\n        self.inner.metadata(path)\n    }\n\n    fn read_file(&self, path: &Path) -> io::Result<Vec<u8>> {\n        if self.spool.owns(path) {","sourceCodeStart":136,"sourceCodeEnd":172,"githubUrl":"https://github.com/sinelaw/fresh/blob/67894ca5463dbd7a89bb31add4627c27d6b79d83/crates/fresh-editor/src/services/stdin_spool.rs#L136-L172","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\nlet data = spool.read_range(path, offset, len)?;\n// after\nlet available = spool.len(path).saturating_sub(offset);\nlet len = len.min(available);\nlet data = spool.read_range(path, offset, len)?;","handlingStrategy":"validation","validationCode":"let spool_len = spool.len(path)?;\nif offset + len > spool_len { len = spool_len - offset; } // clamp before reading","typeGuard":null,"tryCatchPattern":"match spool.read_range(path, offset, len) {\n    Ok(data) => { /* use data */ }\n    Err(e) if e.kind() == io::ErrorKind::UnexpectedEof => { /* treat as EOF */ }\n    Err(e) => return Err(e),\n}","preventionTips":["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"],"tags":["io","eof","stdin"],"backgroundTag":"file-read-failed","analyzedSha":"67894ca5463dbd7a89bb31add4627c27d6b79d83","analyzedAt":"2026-09-13T15:04:03.701Z","contentChangedAt":"2026-09-13T15:04:03.701Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}