fix: resolve Vitest's dist directory case-insensitively on Windows so only one runtime instance loads - #10843
Open
emerson-d-lopes wants to merge 3 commits into
Conversation
6 tasks
✅ Deploy Preview for vitest-dev ready!Built without sensitive environment variables
To edit notification comments on pull requests, go to your Netlify project configuration. |
…zing On Windows a path keeps the drive letter in the case it was written in. A local install resolves the CLI through the working directory, so after `cd /d c:\project` Vitest is loaded from `c:\...\vitest\dist\index.js` while Vite normalizes module ids to `C:/...`. The resolver built the externalized specifier from the id, so the test file imported a second copy of the runtime whose collector state was never populated and the first describe() threw. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Without the guard the new case passes vacuously on a UNC checkout, where there is no drive letter to lowercase. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The earlier version only reconciled the drive letter. distDir keeps the case the CLI was invoked with, while ids carry the working directory's case with an uppercase drive, so any component can differ, not just the drive. Match the dist directory case-insensitively on Windows and hand back the spelling Vitest was loaded with. Gated to Windows, since elsewhere two spellings are two different files. Adds a second regression case for an entirely lowercase CLI path, and asserts the exit code and both error spellings. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
emerson-d-lopes
force-pushed
the
fix/windows-drive-letter-vitest-externalize
branch
from
August 7, 2026 12:28
5f68fb4 to
2a20935
Compare
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.
Description
Resolves #10692.
On Windows a path keeps the drive letter in the case it was written in.
npx vitestresolves the local binary through the working directory, so aftercd /d c:\projectVitest itself is loaded fromc:\project\node_modules\vitest\dist\index.js, while Vite normalizes module ids toC:/project/....getCachedVitestImportbuilds the externalized specifier from the id, so the test file'simport { describe } from 'vitest'becomesfile:///C:/project/node_modules/vitest/dist/index.js. Node keys its module registry on the URL string, so that is a different module from thefile:///c:/project/...copy the worker already loaded. The test file then gets a second copy of the runtime, one that never went throughclearCollectorContext, so its module-levelrunneranddefaultSuiteare undefined and the firstdescribe()fails with eitheror
The fix makes the resolver recognize Vitest's own dist directory regardless of case and hand back the spelling Vitest was actually loaded with, so every spelling maps to one module instance. It is gated to Windows, since on a case-sensitive filesystem two spellings really are two files.
Reproduction
cmd.exepreserves the drive letter as typed. PowerShell uppercases it onSet-Location, so it cannot show this, and--root c:\...cannot either because an explicit root is normalized. The lowercase drive has to arrive through the working directory.Reproduced on 4.1.10 and on 5.0.0-beta.7, Node 24.15.0, Windows 11 Pro 26200.
Tests
test/e2e/test/windows-drive-case.test.tsalready covered a lowercase root, which passes both before and after this change:runVitestClispawns the CLI by its real path, so Vitest is still loaded with the canonical drive letter and onlyconfig.rootdiffers. The case that breaks is Vitest being loaded through the lowercase drive, which is what a local install does. The added test spawns the CLI through a lowercased path with the fixture as the working directory.The added cases cover two spellings, a lowercase drive letter and an entirely lowercase CLI path, and assert the exit code as well as both error spellings. Both fail on
mainand pass with this change. The pre-existing case in that file passes either way.Run on Windows 11 Pro 26200, Node 24.15.0:
cd test/e2e && pnpm exec vitest run windows-drive-case— 3 passed with the change, 2 of the 3 fail with the resolver reverted tomaincd test/unit && pnpm exec vitest run— 627 files, 7379 tests, greenpnpm run test:ci— 43 files failed, 133 passed. I then ran the same command on a clean checkout of the parent commit and got the identical result: the same 43 files, the same 40 failing test names, all in the coverage suite. Diffing the two failure lists gives an empty set in both directions, so none of it comes from this change. I have not tried to work out why the coverage suite fails on this machine.pnpm exec eslinton both changed files — cleanpnpm exec tsc --noEmit -p packages/vitest/tsconfig.json— the twoCDPSession.senderrors inpackages/vitest/src/node/pools/browser.tsare present on the parent commit as wellBoth tests are gated on
process.platform === 'win32'.Scope and limits
Only the drive letter can differ. Windows resolves the rest of the path to its real casing, which I checked by starting a shell with an entirely lowercase path and printing
process.cwd().Why this layer
The authoritative fact is the URL Node used to load Vitest, and that is fixed by how the CLI was invoked. Normalizing
config.rootdoes not help: with an explicit canonical root and a lowercase working directory the failure is unchanged.So the mismatch has to be reconciled where the externalized specifier is built, against the path Vitest was actually loaded from.
The two spellings can differ in more than the drive letter.
distDiris derived fromimport.meta.url, which keeps whatever case the CLI path was written in, while ids come from Vite and carryprocess.cwd()'s case with an uppercase drive. Windows canonicalizes the components of a working directory but not of a path passed tonode, so the mismatch is not limited to one character. The matching is therefore case-insensitive over the whole dist directory rather than a drive-letter rewrite.Things this deliberately does not cover:
bareVitestRegexpbranch hands the bare specifier to Node, so there is no path to rewrite. Making it resolve throughdistDirwould mean returning an absolute URL and changing how subpath exports resolve, which seemed like the wrong trade.file://id path the vm pool produces does go through the same helper now.Unrelated, spotted while reading the neighbours and left alone:
runtime/workers/native.ts:71-72,159andpackages/browser/src/node/index.ts:373comparedistDir(a backslash path fromnode:path.resolve) againstfile://URLs, so on Windows those checks look like they can never match.node/plugins/mocks.ts:12normalizes first and is fine. Happy to open a separate issue if that is useful.I have not confirmed whether this also explains #10812, which reports the same
reading 'config'variant intermittently at full-suite scale. It looked close enough to be worth mentioning, but I did not verify it.Please don't delete this checklist! Before submitting the PR, please make sure you do the following:
pnpm-lock.yamlunless you introduce a new test example.Tests
pnpm test:ci.Documentation
pnpm run docscommand.Changesets
feat:,fix:,perf:,docs:, orchore:.AI disclosure, per CONTRIBUTING.md: I used Claude Code as an assistant while investigating this. The reproduction, the bisect down to
getCachedVitestImport, and the before/after runs were done on my own Windows machine, and I can answer questions about any part of it.