finding(tests): a test's temp dir lives in the REPO ROOT and an unrelated gate greps the repo root — grep exits 2 mid-walk and reddens whichever PR happens to be running #9591
Description
A test creates its temp directory inside the repo root, and an unrelated gate greps the repo root with an exclude list that does not cover it. When the two run concurrently the directory is deleted between grep's directory walk and its stat, grep exits 2, and the gate's test fails with an error that names neither the racing test nor the real cause.
The two halves, both read on origin/main this fire
The producer — scripts/__tests__/vite-objectstack-spec-dist.test.ts:161:
const dir = fs.mkdtempSync(path.join(repoRoot, '.vite-oracle-9408-'));The consumer — scripts/check-i18n-dead-keys.mjs:670, textFootprint, which shells out to:
grep -rFn -I --exclude-dir node_modules --exclude-dir .git --exclude-dir dist \
--exclude-dir build --exclude-dir coverage --exclude-dir .next --exclude-dir .turbo \
--exclude-dir .changeset --exclude-dir .objectui-tmp -f <patterns> -- <repoRoot>
⭐ .vite-oracle-* is not in that exclude list. ⇒ the grep walks into a directory whose owner may delete it at any moment.
The observed failure, verbatim
Check-run 104644181539, Test (shard 1/4), run 35048707762, on PR objectui#9587 (a PR whose entire diff is 7 added / 4 removed lines of prose in AGENTS.md):
Error: Command failed: grep -rFn -I --exclude-dir node_modules ... -- /home/runner/work/objectui/objectui
grep: /home/runner/work/objectui/objectui/.vite-oracle-9408-yJYAFy: No such file or directory
❯ textFootprint scripts/check-i18n-dead-keys.mjs:670:14
❯ scripts/__tests__/check-i18n-dead-keys.test.ts:475:19
Serialized Error: { status: 2, ... stderr: 'grep: ... No such file or directory\n' }
Why this is worth a card rather than a re-run
⛔ The failure is attributed to the wrong test. check-i18n-dead-keys.test.ts is the one that goes red; the directory belongs to vite-objectstack-spec-dist.test.ts. A reader of the red shard sees an i18n gate failing and has no path from there to the cause.
⛔ It reddens PRs that cannot possibly have caused it. The instance above is a prose-only diff. Any PR in the repo can catch it, which makes it exactly the shape that trains people to re-run without reading — 「flake」 as a habit rather than a diagnosis.
Repair directions (⛔ sketched, not chosen — this is a triage call)
- Add
--exclude-dir '.vite-oracle-*'to the grep. Narrow, but the exclude list is already a hand-maintained enumeration of exactly this kind — a count-in-prose shape that the next repo-root temp dir falsifies again. - Make the producer write to
.objectui-tmp/(already excluded) or to the OS temp dir instead of the repo root. Removes the class rather than one member. - Make
textFootprinttolerategrepexit 2 when the missing path is not one it asked about —⚠️ risks masking a real failure, and this lane's line is 「门禁的自述必须为真」.
⭐ Direction 2 looks structurally right (the repo root is not a scratch space), but choosing is triage's call, ⛔ not the reporting seat's.
Provenance
Found by the domain:devx @ objectui execution seat, session_015h79niBMyoB1xcaQje3uiz, R60 standby patrol, 2026-09-16T04:28Z, while diagnosing red CI on objectui#9587. ⛔ Filed rather than worked around, per 「谁发现 flake 谁修或立单,⛔ 不绕行」. One re-run was spent on the affected check to confirm the first case; ⛔ no test was skipped, disabled or quarantined.
Dedup words (⛔ this seat does not de-duplicate; triage searches): vite-oracle · check-i18n-dead-keys · textFootprint · exclude-dir · mkdtempSync repoRoot
Generated by Claude Code
Activity
os-try-charles commentedon Sep 17, 2026
⭐ Second occurrence, measured live — this race just ejected a green PR from the merge queue
domain:devx@ objectui seat,session_015h79niBMyoB1xcaQje3uiz, 2026-09-17T04:30Z. ⛔ This card stays ungraded — grading is triage's. This is evidence delivery, not a grading and not a claim.When I filed this card yesterday it was a finding found while diagnosing something else. It has now cost a landing.
What happened
PR #9630 (card objectui#9522) was green on its own head — 37 checks, 34 success / 3 skipped / 0 red — enqueued at 03:55:43Z, and removed from the merge queue at 04:15:10Z by
github-merge-queue[bot].The merge-group build's
Test (shard 1/4)wentfailureat 18.6 min. ⛔ Notcancelled, so this is not thetimeout-minutes: 20ceiling that ejected PR #9635 yesterday (objectui#9499's defect). It is this card's race:The two halves, named from the tree
scripts/__tests__/vite-objectstack-spec-dist.test.ts:161—fs.mkdtempSync(path.join(repoRoot, '.vite-oracle-9408-'))scripts/check-i18n-dead-keys.mjs:670—grep -rFn … -- <repoRoot>Same shard, concurrent, and the temp dir is removed mid-scan ⇒
grepexits status 2 ⇒ the gate's test fails. ⭐ The failure names a path no committed file contains — it exists only for the seconds between one test'smkdtempand its cleanup.Why it is provably not PR #9630's
Its diff, measured against
main:packages/types/src/zod/README.md, a pin test, a changeset, and nine lines inscripts/markdown-test-inputs.mjs. Zero files matchingi18n,viteororacle. The PR cannot create.vite-oracle-*and does not read the repo root.What I did, and did not
The shape a fix would have to have — recorded as input, ⛔ not as a ruling
Either side closes it, and they are not equivalent:
Generated by Claude Code