Skip to content

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 into
mainfrom
claude/issue-9468-repo-root-scratch-dir-race
Sep 17, 2026
Merged

os-try-charles merged 2 commits into
mainfrom
claude/issue-9468-repo-root-scratch-dir-race

Conversation

@os-try-charles

@os-try-charles os-try-charles commented Sep 17, 2026 •

Copy link
Copy Markdown
Collaborator

Fixes #9468

The race, reproduced before it was repaired

viteResolves() in scripts/__tests__/vite-objectstack-spec-dist.test.ts made its throwaway Vite 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 a single grep -rFn pass. In the same shard, grep enumerates the scratch directory, fails to descend into it, and exits 2 — and textFootprint()'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:

leg scratch dir result
control none present rounds 3, ok 3, status2 0
failing .vite-oracle-9408-* in the repo root rounds 3, ok 0, status2 3
after the fix .vite-oracle-9408-* under node_modules/ rounds 3, ok 3, status2 0
failing .tmp-citation-gate-* in the repo root rounds 3, ok 0, status2 3
after the fix .tmp-citation-gate-* under node_modules/ rounds 3, ok 3, status2 0

The failing leg reproduces the CI message character for character but for the random suffix:

grep: /home/user/objectui-issue-9468/.vite-oracle-9408-tzczpE: No such file or directory
Serialized Error: { status: 2, … }

Why the scratch root moved to node_modules/ rather than to os.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 on viteResolves() states that the root lives inside the repository because bare-specifier resolution walks up from the importer looking for node_modules, and from /tmp there is none. Measured, not inherited — moving it to os.tmpdir() and running the suite:

AssertionError: Vite oracle resolved nothing — broken instrument: expected null to be truthy
 Test Files  1 failed (1)   Tests  1 failed | 39 passed (40)

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_modules is the placement that satisfies both constraints at once: still inside the repository, so the walk-up finds a node_modules; and inside a directory every repo-wide scanner under scripts/ already skips, so nothing transient placed there is walked by any of them. scripts/__tests__/check-action-ref-convention.test.ts already 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 HEAD with git diff HEAD empty afterwards.

pin ablation result
the oracle's scratch root is never a repo-root entry put the placement back in the repo root red: expected '.vite-oracle-9408-fq9i4U' to be 'node_modules'
the sweep never descends into node_modules drop node_modules from TEXT_SWEEP_SKIP_DIRS red: expected [ …(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, and dist is 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

check verdict
vitest run --project unit on the two deciding suites Test Files 2 passed (2) / Tests 175 passed (175)
vitest run --project unit on the citation gate's suite Test 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:scripts exit 0; tsc --listFiles confirms all three changed files are in the program
pnpm check:control-bytes OK (scanned 7785 tracked text file(s))
pnpm check:test-path-roots OK
pnpm check:new-line-citations 0 new citation(s) -> exit 0
pnpm check:i18n-dead-keys exit 0
node scripts/check-changeset-presence.mjs No 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-any warnings, 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.ts built the throwaway git repo for its exits 0 on findings under report-only and 1 under --strict test the same way, with mkdtempSync straight into the repo root. It is not a theoretical twin — the same reproduction against that prefix fails identically:

rounds 3, ok 0, status2 3
grep: …/.tmp-citation-gate-pF6w0r: No such file or directory

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 the git init that 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-tmp is). That is now moot for these two, since node_modules is 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

…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
@github-actions github-actions Bot added the tests label Sep 17, 2026
…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
os-try-charles marked this pull request as ready for review September 17, 2026 06:20
@os-try-charles
os-try-charles added this pull request to the merge queue Sep 17, 2026
Merged via the queue into main with commit e896c38 Sep 17, 2026
35 checks passed
@os-try-charles
os-try-charles deleted the claude/issue-9468-repo-root-scratch-dir-race branch September 17, 2026 06:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

2 participants