GitoxideLabs/gitoxide · warning
we have read non-zero bytes before
Error message
we have read non-zero bytes before
What it means
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.
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.
Example fix
// before let iter = reverse(file); // file concurrently truncated mid-iteration -> panic // after let snapshot = std::fs::read(log_path)?; // stable copy let iter = reverse(std::io::Cursor::new(snapshot));
Defensive patterns
Strategy: retry
Validate before calling
// verify the reflog file is stable before reverse iteration
let meta1 = std::fs::metadata(log_path)?;
std::thread::sleep(std::time::Duration::from_millis(50));
let meta2 = std::fs::metadata(log_path)?;
if meta1.len() != meta2.len() { /* file is being rewritten; wait */ } Try / catch
// snapshot first, then iterate; on failure retry once with a fresh snapshot
match iterate_reverse(snapshot()) {
Ok(lines) => lines,
Err(_) => iterate_reverse(snapshot())?,
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: Reading reflogs of repositories being actively rewritten by other processes; file systems with unreliable size reporting; corrupted `.git/logs/**` files.
Understand the failure class
Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.
Related errors
- BUG: Invalid state: we never discard only our file, always…
- we are called from a valid ref
- parse validation
- name conversion infallible
- always with parent directory
AI-assisted analysis of GitoxideLabs/gitoxide@e73179060b (2026-09-08).
Data as JSON: /api/errors/305d85f8bb94ec28.
Report an issue: GitHub.
Appendix: source
Thrown at gix-ref/src/store/file/log/iter.rs:183
fn next(&mut self) -> Option<Self::Item> {
match (self.last_nl_pos.take(), self.read_and_pos.take()) {
// Initial state - load first data block
(None, Some((mut read, pos))) => {
let npos = pos.saturating_sub(self.buf.len() as u64);
if let Err(err) = read.seek(std::io::SeekFrom::Start(npos)) {
return Some(Err(err.into()));
}
let n = (pos - npos) as usize;
if n == 0 {
return None;
}
let buf = &mut self.buf[..n];
if let Err(err) = read.read_exact(buf) {
return Some(Err(err.into()));
}
let last_byte = *buf.last().expect("we have read non-zero bytes before");
self.last_nl_pos = Some(if last_byte != b'\n' { buf.len() } else { buf.len() - 1 });
self.read_and_pos = Some((read, npos));
self.next()
}
// Has data block and can extract lines from it, load new blocks as needed
(Some(end), Some(read_and_pos)) => match self.buf[..end].rfind_byte(b'\n') {
Some(start) => {
self.read_and_pos = Some(read_and_pos);
self.last_nl_pos = Some(start);
let buf = &self.buf[start + 1..end];
let res = Some(
log::LineRef::from_bytes(buf)
.map_err(|err| {
reverse::Error::Decode(decode::Error::new(err, LineNumber::FromEnd(self.count)))
})
.map(Into::into),
);
self.count += 1;View on GitHub (pinned to e73179060b)