test/sha256-repo.test.ts > resolveRevision turns user input into one full id, or refuses (R0-03) > refuses an ambiguous prefix rather than picking one has two separate problems. The first is measurable and certain; the second is observed once and I cannot explain its mechanism.
1. It passes without asserting, about one run in six
The test commits up to 120 times looking for two commit ids that share a 3-hex prefix, then returns early if it finds none:
for (let i = 0; i < 120 && ambiguous === null; i += 1) { … }
if (ambiguous === null) return; // reported as a pass
120 draws into 4096 buckets leaves a 17.2% chance of no collision. In those runs the test reports green having exercised nothing — neither git's ambiguity detection nor resolveRevision's refusal.
The comment above the loop shows this was a deliberate choice — skipping rather than faking is the right instinct when a search fails. But the skip is invisible: vitest counts it as a pass, so a green suite does not distinguish the refusal works from we never looked. That is the same shape as the ENOENT assertion fixed in #641 — a check that cannot report the thing it appears to check.
A cheaper and more reliable construction is available: git reports an ambiguous short name when the prefix matches multiple objects of any type, so writing blobs with git hash-object -w --stdin until one collides with the HEAD commit's prefix costs no commits, no trees, and no index writes, and can search far enough that failing to find a collision is negligible rather than routine.
2. A staged object vanished mid-loop, once, on CI
Run 31771047613, job check (22.23.2), on PR #644:
FAIL test/sha256-repo.test.ts > … > refuses an ambiguous prefix rather than picking one
Error: git commit --quiet --no-verify -m c105 failed (exit 1):
error: invalid object 100644 1da3827fbab6786665dbd0a1df4272cb81b0b8fe for 'c102.txt'
error: Error building trees
Tests 1 failed | 3064 passed | 2 skipped (3067)
c102.txt was staged three iterations earlier by stageChange, which uses git add and throws on a non-zero exit — so the blob existed when it was staged. By the time c105 built its tree, the object was gone from the database.
What I checked and ruled out:
- Not a shared temp directory.
temp() uses mkdtempSync, so every repo is unique; there is no glob-based sweep of tmpdir() anywhere in test/, src/, or scripts/.
- Not the product calling gc. The only
git gc in the tree is test/deterministic-bench.test.ts, scoped to its own fixture.
- Not automatic gc, as far as I can tell.
gc.auto defaults to 6700 loose objects; this loop creates roughly 360.
What the measurements say about scope:
I cannot explain how the object disappeared. Recording it rather than closing it, because a git object database losing a just-written object inside a test is worth knowing about even at one occurrence, and the honest state is unexplained rather than benign.
The two problems are plausibly connected — 120 commits with a growing index is the heaviest thing this file does, and replacing it with blob hashing would remove the stressor as well as the silent pass — but I have not shown that the loop caused the failure, and fixing the loop should not be recorded as fixing the object loss.
test/sha256-repo.test.ts>resolveRevision turns user input into one full id, or refuses (R0-03)>refuses an ambiguous prefix rather than picking onehas two separate problems. The first is measurable and certain; the second is observed once and I cannot explain its mechanism.1. It passes without asserting, about one run in six
The test commits up to 120 times looking for two commit ids that share a 3-hex prefix, then returns early if it finds none:
120 draws into 4096 buckets leaves a 17.2% chance of no collision. In those runs the test reports green having exercised nothing — neither git's ambiguity detection nor
resolveRevision's refusal.The comment above the loop shows this was a deliberate choice — skipping rather than faking is the right instinct when a search fails. But the skip is invisible: vitest counts it as a pass, so a green suite does not distinguish the refusal works from we never looked. That is the same shape as the
ENOENTassertion fixed in #641 — a check that cannot report the thing it appears to check.A cheaper and more reliable construction is available: git reports an ambiguous short name when the prefix matches multiple objects of any type, so writing blobs with
git hash-object -w --stdinuntil one collides with the HEAD commit's prefix costs no commits, no trees, and no index writes, and can search far enough that failing to find a collision is negligible rather than routine.2. A staged object vanished mid-loop, once, on CI
Run 31771047613, job
check (22.23.2), on PR #644:c102.txtwas staged three iterations earlier bystageChange, which usesgit addand throws on a non-zero exit — so the blob existed when it was staged. By the timec105built its tree, the object was gone from the database.What I checked and ruled out:
temp()usesmkdtempSync, so every repo is unique; there is no glob-based sweep oftmpdir()anywhere intest/,src/, orscripts/.git gcin the tree istest/deterministic-bench.test.ts, scoped to its own fixture.gc.autodefaults to 6700 loose objects; this loop creates roughly 360.What the measurements say about scope:
check (24)passed on the same run, same commit.check (22.23.2)passed on Do not advertise capture tools a runtime cannot serve #641 and on docs: correct four factual defects in the README, and fix the hero SVG #643 the same day, so this is not a property of Node 22.23.2.Error building trees/FAIL test/sha256-repo.test.ts) appears in no other failing run in the last 40.I cannot explain how the object disappeared. Recording it rather than closing it, because a git object database losing a just-written object inside a test is worth knowing about even at one occurrence, and the honest state is unexplained rather than benign.
The two problems are plausibly connected — 120 commits with a growing index is the heaviest thing this file does, and replacing it with blob hashing would remove the stressor as well as the silent pass — but I have not shown that the loop caused the failure, and fixing the loop should not be recorded as fixing the object loss.