From f4c7143525b7a8f3e6783c2b79d11a0c5e9f96b8 Mon Sep 17 00:00:00 2001 From: Marius Ciepluch <11855163+norandom@users.noreply.github.com> Date: Thu, 8 Oct 2026 11:16:13 +0200 Subject: [PATCH] unseen: make clone disk-size guard tolerate git's mid-walk churn disk_bytes() summed the clone tree with rglob + stat() as a size cap before every git command. On a blob:none clone, `git show` triggers an on-demand blob fetch that writes and deletes transient .git/objects/pack/*.rev files, so a path rglob had just listed could be gone by the stat() call -> FileNotFoundError aborted the whole draft as an instrument_failure. The race can hit any candidate, so it was a non-deterministic draft-killer (observed extracting a real npm repo). Skip a file that raises OSError mid-walk; the size cap is unaffected. Co-Authored-By: Claude Opus 4.8 --- benchmarks/unseen/extract.py | 11 ++++++++++- tests/test_unseen_extract.py | 22 ++++++++++++++++++++++ 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/benchmarks/unseen/extract.py b/benchmarks/unseen/extract.py index 0e7a5a5..e61c474 100644 --- a/benchmarks/unseen/extract.py +++ b/benchmarks/unseen/extract.py @@ -57,7 +57,16 @@ class Git: return done.stdout.decode("utf-8", errors="replace") def disk_bytes(self) -> int: - return sum(p.stat().st_size for p in self.path.rglob("*") if p.is_file() and not p.is_symlink()) + # git churns .git/objects/pack (incl. transient *.rev) during on-demand blob + # fetches, so a path rglob just listed can vanish before stat(): skip it. + total = 0 + for p in self.path.rglob("*"): + try: + if p.is_file() and not p.is_symlink(): + total += p.stat().st_size + except OSError: + continue + return total def parents(self, sha: str) -> list[str]: return self.run("rev-list", "--parents", "-n", "1", sha).split()[1:] diff --git a/tests/test_unseen_extract.py b/tests/test_unseen_extract.py index be81071..b84e2e7 100644 --- a/tests/test_unseen_extract.py +++ b/tests/test_unseen_extract.py @@ -312,3 +312,25 @@ def test_unbalanced_cells_remain_proportional_and_seed_changes_draw(): draw, _ = e.draw_ordinary(rows, set(), set(), seed=1) assert len([r for r in draw if r["size_bucket"] == 0]) == 9 assert draw != e.draw_ordinary(rows, set(), set(), seed=2)[0] + + +def test_disk_bytes_skips_files_that_vanish_mid_walk(tmp_path): + # git churns .git/objects/pack (transient *.rev) during on-demand blob fetches, + # so a path rglob just listed can be gone by the time disk_bytes stat()s it. + git = e.Git(tmp_path, runner=subprocess.run) + (tmp_path / "a").write_bytes(b"x" * 10) + (tmp_path / "b").write_bytes(b"y" * 20) + real = [p for p in tmp_path.rglob("*")] + + class Vanished: + def is_file(self): + return True + + def is_symlink(self): + return False + + def stat(self): + raise FileNotFoundError(2, "No such file or directory") + + git.path = type("P", (), {"rglob": lambda self, pattern: iter([*real, Vanished()])})() + assert git.disk_bytes() == 30