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.
⚠️ The mechanism is deterministic; only the timing is not. ⛔ So it does not age out, and it will not be fixed by whoever happens to hit it next unless it is written down.
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
textFootprint tolerate grep exit 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
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 itsstat,grepexits 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/mainthis fireThe producer —
scripts/__tests__/vite-objectstack-spec-dist.test.ts:161:The consumer —
scripts/check-i18n-dead-keys.mjs:670,textFootprint, which shells out to:⭐
.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), run35048707762, on PR objectui#9587 (a PR whose entire diff is 7 added / 4 removed lines of prose inAGENTS.md):Why this is worth a card rather than a re-run
⛔ The failure is attributed to the wrong test.
check-i18n-dead-keys.test.tsis the one that goes red; the directory belongs tovite-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)
--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..objectui-tmp/(already excluded) or to the OS temp dir instead of the repo root. Removes the class rather than one member.textFootprinttolerategrepexit 2 when the missing path is not one it asked about —⭐ 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 repoRootGenerated by Claude Code