{"record":{"id":"305d85f8bb94ec28","repo":"GitoxideLabs/gitoxide","slug":"we-have-read-non-zero-bytes-before","errorCode":null,"errorMessage":"we have read non-zero bytes before","messagePattern":"we have read non-zero bytes before","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"gix-ref/src/store/file/log/iter.rs","lineNumber":183,"sourceCode":"    fn next(&mut self) -> Option<Self::Item> {\n        match (self.last_nl_pos.take(), self.read_and_pos.take()) {\n            // Initial state - load first data block\n            (None, Some((mut read, pos))) => {\n                let npos = pos.saturating_sub(self.buf.len() as u64);\n                if let Err(err) = read.seek(std::io::SeekFrom::Start(npos)) {\n                    return Some(Err(err.into()));\n                }\n\n                let n = (pos - npos) as usize;\n                if n == 0 {\n                    return None;\n                }\n                let buf = &mut self.buf[..n];\n                if let Err(err) = read.read_exact(buf) {\n                    return Some(Err(err.into()));\n                }\n\n                let last_byte = *buf.last().expect(\"we have read non-zero bytes before\");\n                self.last_nl_pos = Some(if last_byte != b'\\n' { buf.len() } else { buf.len() - 1 });\n                self.read_and_pos = Some((read, npos));\n                self.next()\n            }\n            // Has data block and can extract lines from it, load new blocks as needed\n            (Some(end), Some(read_and_pos)) => match self.buf[..end].rfind_byte(b'\\n') {\n                Some(start) => {\n                    self.read_and_pos = Some(read_and_pos);\n                    self.last_nl_pos = Some(start);\n                    let buf = &self.buf[start + 1..end];\n                    let res = Some(\n                        log::LineRef::from_bytes(buf)\n                            .map_err(|err| {\n                                reverse::Error::Decode(decode::Error::new(err, LineNumber::FromEnd(self.count)))\n                            })\n                            .map(Into::into),\n                    );\n                    self.count += 1;","sourceCodeStart":165,"sourceCodeEnd":201,"githubUrl":"https://github.com/GitoxideLabs/gitoxide/blob/e73179060badf27222d790981fac3f84c1830a7e/gix-ref/src/store/file/log/iter.rs#L165-L201","documentation":"This is a Rust `expect()` panic in the `Reverse` reflog iterator's `next()` (gix-ref/store/file/log/iter.rs). After seeking backwards and calling `read_exact` for `n` bytes, the code calls `buf.last()`; the `expect` asserts `n` is non-zero because `n == 0` was returned early above. It can only panic if `n` was >0 but `read_exact` left the buffer logically empty relative to expectations — i.e. an arithmetic/seek invariant broke, typically from a corrupted reflog file size or a race where the file shrank between size query and read.","triggerScenarios":"Iterating a reflog in reverse (`gix_ref::store::file::log::iter::reverse`) while the reflog file is truncated/concurrently modified (e.g. `git gc`, ref deletion) between the stat that computed `pos` and the `read_exact`; or a reflog whose reported size is inconsistent.","commonSituations":"Reading reflogs of repositories being actively rewritten by other processes; file systems with unreliable size reporting; corrupted `.git/logs/**` files.","solutions":["Do not iterate reflogs concurrently with operations that rewrite them; take a stable snapshot (copy the log file) first.","Check the reflog file for truncation/corruption and repair it (e.g. restore via `git reflog` on a healthy clone, or delete the corrupt log).","Retry the iteration after the concurrent writer finishes (reflog rewrite is transient).","If it happens on static files, report upstream with the reflog file — internal invariant violation."],"exampleFix":"// before\nlet iter = reverse(file); // file concurrently truncated mid-iteration -> panic\n// after\nlet snapshot = std::fs::read(log_path)?; // stable copy\nlet iter = reverse(std::io::Cursor::new(snapshot));","handlingStrategy":"retry","validationCode":"// verify the reflog file is stable before reverse iteration\nlet meta1 = std::fs::metadata(log_path)?;\nstd::thread::sleep(std::time::Duration::from_millis(50));\nlet meta2 = std::fs::metadata(log_path)?;\nif meta1.len() != meta2.len() { /* file is being rewritten; wait */ }","typeGuard":null,"tryCatchPattern":"// snapshot first, then iterate; on failure retry once with a fresh snapshot\nmatch iterate_reverse(snapshot()) {\n    Ok(lines) => lines,\n    Err(_) => iterate_reverse(snapshot())?,\n}","preventionTips":["Don't iterate reflogs while gc/rebase/filter-branch may rewrite them","Copy the log file before reverse iteration","Treat panics during concurrent repo mutation as a signal to retry after quiescence"],"tags":["rust","panic","reflog","concurrency"],"backgroundTag":"internal-invariant-violation","analyzedSha":"e73179060badf27222d790981fac3f84c1830a7e","analyzedAt":"2026-09-08T11:26:50.865Z","contentChangedAt":"2026-09-08T11:26:50.865Z","schemaVersion":2},"datasetVersion":"2026-09-15T23:17:13.987Z"}