{"record":{"id":"509deb350a3267ce","repo":"astrid-runtime/astrid","slug":"negative-volume-seek","errorCode":null,"errorMessage":"negative volume seek","messagePattern":"negative volume seek","errorType":"exception","errorClass":"io::Error","httpStatus":null,"severity":"error","filePath":"crates/astrid-storage/src/volume.rs","lineNumber":428,"sourceCode":"}\n\nimpl Seek for VolumeFile {\n    fn seek(&mut self, position: SeekFrom) -> io::Result<u64> {\n        let next = match position {\n            SeekFrom::Start(offset) => i128::from(offset),\n            SeekFrom::Current(delta) => i128::from(self.cursor)\n                .checked_add(i128::from(delta))\n                .ok_or_else(|| {\n                    io::Error::new(io::ErrorKind::InvalidInput, \"volume seek overflow\")\n                })?,\n            SeekFrom::End(delta) => i128::from(self.volume.region_len(&self.region)?)\n                .checked_add(i128::from(delta))\n                .ok_or_else(|| {\n                    io::Error::new(io::ErrorKind::InvalidInput, \"volume seek overflow\")\n                })?,\n        };\n        self.cursor = u64::try_from(next)\n            .map_err(|_| io::Error::new(io::ErrorKind::InvalidInput, \"negative volume seek\"))?;\n        Ok(self.cursor)\n    }\n}\n\n/// Minimal metadata returned for a volume region.\n#[derive(Clone, Copy, Debug, PartialEq, Eq)]\npub struct VolumeMetadata {\n    length: u64,\n}\n\nimpl VolumeMetadata {\n    /// Return the logical byte length.\n    #[must_use]\n    pub const fn len(self) -> u64 {\n        self.length\n    }\n\n    /// A volume region is always a regular byte stream.","sourceCodeStart":410,"sourceCodeEnd":446,"githubUrl":"https://github.com/astrid-runtime/astrid/blob/affd8760f44190dbdfbec23403f4c4b642c33112/crates/astrid-storage/src/volume.rs#L410-L446","documentation":"After the overflow checks, seek() converts the computed i128 position back to u64; a negative result means the seek landed before offset 0, which is invalid, so InvalidInput ('negative volume seek') is returned.","triggerScenarios":"SeekFrom::Current(negative delta) or SeekFrom::End(negative delta) whose magnitude exceeds the cursor/region length, e.g. seeking -1000 from Current when cursor is 10.","commonSituations":"Rewinding past the start due to miscounted bytes read; off-by-one in buffer management; using a recorded delta computed against a different, larger position.","solutions":["Clamp the requested position to 0 before seeking","Compute the target in the caller and use SeekFrom::Start(max(0, target))","Verify bookkeeping of how far back you intend to seek (bytes consumed)","Handle the InvalidInput error to detect underflow instead of panicking on assumption"],"exampleFix":"// before\nreader.seek(std::io::SeekFrom::Current(-1000))?; // cursor is only 10\n// after\nlet target = cursor.saturating_sub(1000);\nreader.seek(std::io::SeekFrom::Start(target))?;","handlingStrategy":"validation","validationCode":"fn rewind_target(cursor: u64, back: u64) -> u64 { cursor.saturating_sub(back) } // use SeekFrom::Start(rewind_target(...))","typeGuard":"fn can_rewind(cursor: u64, back: u64) -> bool { back <= cursor }","tryCatchPattern":"match reader.seek(SeekFrom::Current(-back)) {\n    Err(e) if e.kind() == std::io::ErrorKind::InvalidInput => {\n        reader.seek(std::io::SeekFrom::Start(0))? // clamp to start\n    }\n    other => other?,\n}","preventionTips":["Track bytes consumed so rewind distances never exceed the cursor","Use saturating_sub plus SeekFrom::Start for backward seeks","Add assertions that computed positions are >= 0"],"tags":["io","seek","underflow","validation"],"backgroundTag":"argument-out-of-range","analyzedSha":"affd8760f44190dbdfbec23403f4c4b642c33112","analyzedAt":"2026-09-09T21:28:12.402Z","contentChangedAt":"2026-09-09T21:28:12.402Z","schemaVersion":2},"datasetVersion":"2026-09-17T15:17:12.973Z"}