Repository navigation
fix(scripts): the vite resolve oracle stops writing its scratch root into the swept repo root (objectui#9468) - #9658
Merged
os-try-charles merged 2 commits intoSep 17, 2026
Conversation
…into the swept repo root (objectui#9468) `viteResolves()` in `scripts/__tests__/vite-objectstack-spec-dist.test.ts` made its throwaway build root with `mkdtempSync` directly in the repo root, and removed it again seconds later. `textFootprint()` in `scripts/check-i18n-dead-keys.mjs` sweeps that same repo root with one `grep -rFn` pass. Scheduled into the same shard, grep enumerates the scratch directory and then fails to descend into it, exits 2 rather than 1, and `textFootprint()` rethrows every status but 1 on purpose — taking the whole shard down with ~10,500 passing tests in it. The occurrences are recorded on objectui#9468, one of them ejecting an already-green pull request from the merge queue. The repair moves the scratch root under `<repoRoot>/node_modules`, which is the only placement that satisfies both constraints at once: still inside the repository, so the bare-specifier walk-up the oracle depends on still finds a `node_modules` (measured — at `os.tmpdir()` every specifier comes back unresolved and the suite's own anti-vacuity control catches it), and inside a directory every repo-wide scanner in `scripts/` already skips, so nothing transient placed there is ever walked by any of them. `check-action-ref-convention.test.ts` already puts its throwaway root there. ⚠ Chosen over adding a `.vite-oracle-*` entry to the sweep's skip set, which fixes one scanner and leaves the class alive — the skip set is a whitelist that every future scratch directory has to be added to, and objectui#9201 already answered this same class that way once. Two pins, each shown to fail: - the producer asserts the real creation is not a repo-root entry; reverting the placement reports `expected '.vite-oracle-9408-…' to be 'node_modules'`. - the sweep side asserts it genuinely never descends into `node_modules`, which is the property the placement rests on; removing `node_modules` from `TEXT_SWEEP_SKIP_DIRS` turns it red. Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015h79niBMyoB1xcaQje3uiz
…o root too (objectui#9468)
The second producer of the same defect, folded in on the dispatching seat's
authorisation after it was measured rather than argued.
`scripts/__tests__/check-new-cross-file-line-citations.test.ts` built the
throwaway git repo for its "exits 0 on findings under report-only and 1 under
--strict" test with `mkdtempSync` directly in the repo root, exactly as the
vite resolve oracle did. Driving the real `textFootprint()` over the real repo
root while that prefix is created and removed reproduces this card's failure
unchanged:
rounds 3, ok 0, status2 3
grep: …/.tmp-citation-gate-pF6w0r: No such file or directory
Same one-line move as the oracle, to `node_modules`, which every repo-wide
scanner under `scripts/` already skips. The same reproduction with the churn
under `node_modules` instead: `rounds 3, ok 3, status2 0`. The suite is
unchanged at the new placement — `Test Files 1 passed (1) · Tests 34 passed
(34)` — including the `git init` this scratch repo does, which creates its own
repository wherever it is put.
⚠ No positional pin of its own here, deliberately. The gate that would refuse
repo-root scratch state for every producer at once is a new verification
surface and therefore a decision, not this card's repair; it stays recorded on
the card instead.
Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015h79niBMyoB1xcaQje3uiz
os-try-charles
marked this pull request as ready for review
September 17, 2026 06:20
os-try-charles
deleted the
claude/issue-9468-repo-root-scratch-dir-race
branch
September 17, 2026 06:42
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 free
to 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 #9468
The race, reproduced before it was repaired
viteResolves()inscripts/__tests__/vite-objectstack-spec-dist.test.tsmade its throwaway Vite build root withmkdtempSyncdirectly in the repo root and removed it again seconds later.textFootprint()inscripts/check-i18n-dead-keys.mjssweeps that same repo root with a singlegrep -rFnpass. In the same shard, grep enumerates the scratch directory, fails to descend into it, and exits 2 — andtextFootprint()'s catch rethrows every status but 1 deliberately, its comment saying why. The shard dies with ~10,500 passing tests in it.Driving the real
textFootprint()over the real repo root, three legs:rounds 3, ok 3, status2 0.vite-oracle-9408-*in the repo rootrounds 3, ok 0, status2 3.vite-oracle-9408-*undernode_modules/rounds 3, ok 3, status2 0.tmp-citation-gate-*in the repo rootrounds 3, ok 0, status2 3.tmp-citation-gate-*undernode_modules/rounds 3, ok 3, status2 0The failing leg reproduces the CI message character for character but for the random suffix:
Why the scratch root moved to
node_modules/rather than toos.tmpdir()The card offered two shapes. Both were measured; neither is the one that landed, and the reason is a constraint the producer already documented.
os.tmpdir()breaks the oracle. The docstring onviteResolves()states that the root lives inside the repository because bare-specifier resolution walks up from the importer looking fornode_modules, and from/tmpthere is none. Measured, not inherited — moving it toos.tmpdir()and running the suite:The suite's own anti-vacuity control catches it, which is the good case; the bad case is that this is the exact failure direction that reads as a clean pass.
Adding
.vite-oracle-*to the sweep's skip set fixes one scanner and leaves the class alive. That set is a whitelist, and objectui#9201 already answered this same class that way once — this card is its recurrence.repoRoot/node_modulesis the placement that satisfies both constraints at once: still inside the repository, so the walk-up finds anode_modules; and inside a directory every repo-wide scanner underscripts/already skips, so nothing transient placed there is walked by any of them.scripts/__tests__/check-action-ref-convention.test.tsalready puts its throwaway root in the same place.The suite stays green at the new placement:
Test Files 1 passed (1) · Tests 40 passed (40).Two pins, each shown to fail
Both ablations mutated the tree on disk, proved the mutation landed before reading any result, and restored from
HEADwithgit diff HEADempty afterwards.expected '.vite-oracle-9408-fq9i4U' to be 'node_modules'node_modulesnode_modulesfromTEXT_SWEEP_SKIP_DIRSexpected [ …(2) ] to deeply equal [ 'packages/x/src/Reader.tsx' ]The second ablation earned its keep. The first draft of that pin spelled its fixture
node_modules/some-dep/dist/index.js, anddistis in the skip set too — so the ablation left the pin green, pinning nothing. The fixture path now carries exactly one excluded segment, and the test comment records why.Verification
vitest run --project uniton the two deciding suitesTest Files 2 passed (2)/Tests 175 passed (175)vitest run --project uniton the citation gate's suiteTest Files 1 passed (1)/Tests 34 passed (34)vitest run --project unit scripts/__tests__/Test Files 169 passed | 2 skipped (171) · Tests 4870 passed | 2 skipped (4872)pnpm type-check:scriptstsc --listFilesconfirms all three changed files are in the programpnpm check:control-bytesOK (scanned 7785 tracked text file(s))pnpm check:test-path-rootsOKpnpm check:new-line-citations0 new citation(s) -> exit 0pnpm check:i18n-dead-keysnode scripts/check-changeset-presence.mjsNo source or published contract of a released package changed in this range, so no changeset is owed.ESLint over the three changed files: exit 0, 0 errors. The citation gate's file reports 0 warnings; the oracle's file carries 5
no-explicit-anywarnings, all at lines outside every hunk this branch adds (warnings at 117/935/960/1112/1146, hunks at 142-177/188-189/193/220-235), so all pre-existing. This is a narrowed run, not the repo-wide one: the repo-wide lint is CI's, and the config carries no type-aware linting, so a change inside these three files cannot move the verdict on a file the branch does not touch.All figures above were measured at
73dfdda84, the branch head this PR carries.Acceptance notes
This branch repairs both repo-root scratch directories, so the claim is "removes the class as it exists in this tree" rather than "removes one instance".
scripts/__tests__/check-new-cross-file-line-citations.test.tsbuilt the throwaway git repo for itsexits 0 on findings under report-only and 1 under --stricttest the same way, withmkdtempSyncstraight into the repo root. It is not a theoretical twin — the same reproduction against that prefix fails identically:The same one-line move repairs it, and the suite is unchanged at the new placement:
Test Files 1 passed (1)/Tests 34 passed (34)— including thegit initthat scratch repo performs, which creates its own repository wherever it is put.⚠ That producer gets no positional pin of its own. The thing that would pin every producer at once is a gate refusing repo-root scratch state, and that is a new verification surface — a decision, not this card's repair. It stays recorded on objectui#9468 for its own card.
Noted, not filed — neither
.vite-oracle-*nor.tmp-citation-gate-*is in.gitignore(only.objectui-tmpis). That is now moot for these two, sincenode_modulesis ignored, but it stays recorded as the same decision the gate above would be: what the tree owes is a rule about where scratch state may live, not two more ignore entries. Carrier: the class card.Generated by Claude Code
Generated by Claude Code