Uh oh!
There was an error while loading. Please reload this page.
test(spec): harden the build-schemas check-mode fixture repos against the merge-queue lifetime race - #9104
Merged
Conversation
…per-block (#9068) A merge-queue full-suite run lost ten tests in this file to one root error — `fatal: Not a valid commit name <sha>` on a commit the fixture had created itself minutes earlier. The fixture repos inherited ambient git config and any leaked GIT_* pointer, and three describe blocks share one repo that they mark shallow; a gc arriving in that window prunes everything behind the graft point. Reproduced and closed, without changing what any test asserts. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01225pUjnCKWqxcc1PeqKFUq
Contributor
📓 Docs Drift CheckNo hand-written docs reference the 0 changed package(s). ✅ |
This was referenced Aug 16, 2026
os-zhuang
marked this pull request as ready for review
August 16, 2026 10:46
Uh oh!
There was an error while loading. Please reload this page.
akarma-synetal pushed a commit
to akarma-synetal/framework
that referenced
this pull request
Aug 17, 2026
…es hermetic (objectstack-ai#9168) Applies the objectstack-ai#9068/objectstack-ai#9104 hermetic-git pattern to the two harnesses issue objectstack-ai#9109 names: the spec sharded-artifacts fixture repos, and the create-objectstack repo reads. No assertion changes — only what the git invocations inherit. Co-authored-by: Claude <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for freeto join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes#9068
Test-harness hardening only. No
*.zod.ts, no strictness-ledger file, no behaviourchange to
build-schemas.ts, and all 60 assertions in the file are preserved verbatim —what changes is how the fixture repositories are built and insulated, never what is
asserted.
What the harness actually looked like (measured, not assumed)
Three of the card's premises were checked against
origin/mainbefore anything was edited:this file already use
fs.mkdtempSync(build-schemas-check-, and threeboxprefixes),packages/spechas exactly one vitest project, and vitest isolates test files intoseparate processes. No concurrently running file can name one of these directories.
cwdand two-c user.*args and inherited everything else: the ambient global andsystem git config,
init.templateDir, and everyGIT_*environment variable. So did thegenerator runs, and
build-schemas.tsshells out to git itself (merge-base,cat-file,a
--depth=1fetch).already serial: vitest runs one test at a time within a file. Not implemented, and the
reason is recorded in the file.
The mechanism, reproduced
Three describe blocks (#5370, #5847, #6452 — exactly the three that failed) deliberately
write
.git/shallowinto the shared sandbox, because that is the environment theymodel: CI checks out
--depth=1. Inside that window every commit behind the graft point isunreachable by definition, so a
git gcarriving there prunes the fixture's own historyand leaves the refs pointing at objects that are gone. On a five-commit fixture in exactly
that state:
That is the reported signature. The control — same commits, same gc, no
.git/shallow— keeps every object.
The fix
Two layers, because they fail differently:
HERMETIC_ENVon every git this file runs and on the generator spawns, so ambientconfig and leaked
GIT_*pointers (GIT_DIR,GIT_INDEX_FILE,GIT_OBJECT_DIRECTORY,GIT_ALTERNATE_OBJECT_DIRECTORIES, …) cannot reach a fixture or its children.initFixtureRepowritesgc.auto=0,gc.autoDetach=false,maintenance.auto=false,gc.pruneExpire=never,gc.reflogExpire*=never,core.logAllRefUpdates=trueinto eachrepo's own config. This is the layer that survives a gc this file never launched:
local config outranks global. Measured A/B — identical outside
git gcwith a hostileglobal config, in the shallow window: unhardened repo loses the commit, hardened repo
keeps it.
Plus the card's preferred ①, applied where it is load-bearing: the three git-topology blocks
each take their own fixture repo (
createSandbox/mountSandbox/releaseSandbox).They fork branches, reset them every
beforeEach, leave merges uncommitted and truncatehistory; sharing one repo is why a single lost object read as ten failures spread over three
blocks, with the block that caused it invisible. A
scripts/copy is 1.8 MB and thegeneration cost is per run, not per sandbox.
Finally, a fixture git failure now names the repo and its state (
HEAD,main,origin/main, shallow, and whethermain's object still exists) instead of a bare sha —the whole first triage lap on this card went into establishing that the sha belonged to no
tree of this repository.
Verification — every run below at
c0740f996, the head of this branchStability, since a hardening PR should demonstrate it rather than assert it:
Run 3 is the parallel-pressure reading: two processes driving the same fixture-building code
at the same moment, which is the shape the merge queue applies.
Reverse verification, direction predicted (red) before it was run. Same subset
(
-t 'a shallow checkout re-anchors', 5 tests), same hostile environment —GIT_DIRaimed ata decoy repository plus a global config carrying
gc.pruneExpire/reflogExpire = now:origin/main's harness → 5 failed / 5Gates. Re-derived from the actual changed path with
node scripts/pm/dispatch-gates.mjs;it named exactly the dispatched set, nothing beyond. Green here:
check:merge-driver,check:type-source-resolution,check:query-options-erasure,check:type-check-coverage,check:engine-double-contract,check:where-matcher,check:nul-bytes, andcheck:scripts-typecheck(the zero-error-budget program this file belongs to).Two are not measured locally, deliberately:
check:type-check-debtandcheck-dev-prereqs.mjsboth refuse on a worktree with no built closure — the ratchet says soitself ("measuring now would not fail, it would silently measure a DIFFERENT WORLD"). Their
population is
src/**/*.test.tsand workspacedist/, neither of which this diff touches, soa full 55-package build here buys no information the CI job will not produce. CI is the
authority on both.
skip-changeset: tests-only, nothing published.Generated by Claude Code