Ray's Knowledge Base

After libgit2 rewrites the index, git status can miss a same-second, same-size edit

PitfallVerified 28 Sep 2026Holds anywhere
Pitfall. The symptom, what causes it, and the fix that was run and seen to work.

Symptom#

A tool built on libgit2 (git2 crate) writes the index, for example to stage another file. Afterwards plain git status does not show a change to a file that was edited in the same second as the previous index write, when the edit kept the file size. The change is still on disk, and git diff misses it too. It happens rarely (1 run in 20 in a loop), so tests that compare with git fail now and then.

Cause#

git trusts an index entry whose size and modification second match the file, unless the entry is "racy": modified in or after the second of the index file itself. When git writes the index, it marks such racy entries as dirty (size 0) after it checks their content, so a later git status reads the file again. libgit2's index write did not mark the entry here. Once libgit2 wrote the index in a later second, the entry was no longer racy for git, and git trusted the old size and second. (My guess is that libgit2 checks racy entries to the nanosecond while git uses whole seconds; I did not confirm this in the libgit2 source.)

Fix#

After every operation that writes the index through libgit2, mark suspect entries the way git does:

  1. Before the operation, read the index file's modification time in whole seconds (since).
  2. After it, if the index file's second is now later than since, reload the index. For each regular-file entry with mtime.seconds() >= since and file_size != 0, whose file on disk has the same size: hash the file (Oid::hash_file(ObjectType::Blob, path)). When the hash differs from the entry id, set entry.file_size = 0 and index.add(&entry).
  3. Write the index if any entry changed.

Run this at one choke point per front end (rgit: after each CLI command, MCP call and TUI mutation), not in each backend function.

Evidence#

  • rgit, 2026-09-27: loop of 20 runs (echo 2 > a; git add a; git commit; echo 3 > a; rgit add -f x.log; git status --short): rgit missed M a once, git itself 0 times.
  • Deterministic test (an_edit_in_the_index_second_stays_visible_after_rgit_writes in crates/rgit-cli/tests/paths.rs): set the file and index modification times by hand inside one past second, edit the file with the same size, then rgit stage x.txt. Without the fix git status --short printed M c.txt (the unstaged change was lost); with it MM c.txt. Fix commit 5d16006.