Skip to content

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
vitest-dev:mainfrom
emerson-d-lopes:fix/windows-drive-letter-vitest-externalize
Open

fix: resolve Vitest's dist directory case-insensitively on Windows so only one runtime instance loads#10843
emerson-d-lopes wants to merge 3 commits into
vitest-dev:mainfrom
emerson-d-lopes:fix/windows-drive-letter-vitest-externalize

Conversation

@emerson-d-lopes

@emerson-d-lopes emerson-d-lopes commented Jul 27, 2026

Copy link
Copy Markdown

Description

Resolves #10692.

On Windows a path keeps the drive letter in the case it was written in. npx vitest resolves the local binary through the working directory, so after cd /d c:\project Vitest itself is loaded from c:\project\node_modules\vitest\dist\index.js, while Vite normalizes module ids to C:/project/....

getCachedVitestImport builds the externalized specifier from the id, so the test file's import { describe } from 'vitest' becomes file:///C:/project/node_modules/vitest/dist/index.js. Node keys its module registry on the URL string, so that is a different module from the file:///c:/project/... copy the worker already loaded. The test file then gets a second copy of the runtime, one that never went through clearCollectorContext, so its module-level runner and defaultSuite are undefined and the first describe() fails with either

Error: Vitest failed to find the current suite. One of the following is possible:

or

TypeError: Cannot read properties of undefined (reading 'config')

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.exe preserves the drive letter as typed. PowerShell uppercases it on Set-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.

> cmd /d /c "cd /d C:\project && npx vitest run"
 Test Files  1 passed (1)

> cmd /d /c "cd /d c:\project && npx vitest run"
TypeError: Cannot read properties of undefined (reading 'config')
 Test Files  1 failed (1)   Tests  no tests

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.ts already covered a lowercase root, which passes both before and after this change: runVitestCli spawns the CLI by its real path, so Vitest is still loaded with the canonical drive letter and only config.root differs. 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 main and 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 to main
  • cd test/unit && pnpm exec vitest run — 627 files, 7379 tests, green
  • pnpm 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 eslint on both changed files — clean
  • pnpm exec tsc --noEmit -p packages/vitest/tsconfig.json — the two CDPSession.send errors in packages/vitest/src/node/pools/browser.ts are present on the parent commit as well

Both 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.root does not help: with an explicit canonical root and a lowercase working directory the failure is unchanged.

> cmd /d /c "cd /d c:\project && npx vitest run --root C:\project"
 RUN  v5.0.0-beta.7 C:/project
TypeError: Cannot read properties of undefined (reading 'config')
 Test Files  1 failed (1)

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. distDir is derived from import.meta.url, which keeps whatever case the CLI path was written in, while ids come from Vite and carry process.cwd()'s case with an uppercase drive. Windows canonicalizes the components of a working directory but not of a path passed to node, 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:

  • The bareVitestRegexp branch hands the bare specifier to Node, so there is no path to rewrite. Making it resolve through distDir would mean returning an absolute URL and changing how subpath exports resolve, which seemed like the wrong trade.
  • Browser mode and the vm pool are untested for this, though the 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,159 and packages/browser/src/node/index.ts:373 compare distDir (a backslash path from node:path.resolve) against file:// URLs, so on Windows those checks look like they can never match. node/plugins/mocks.ts:12 normalizes 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:

  • It's really useful if your PR references an issue where it is discussed ahead of time. If the feature is substantial or introduces breaking changes without a discussion, PR might be closed.
  • Ideally, include a test that fails without this PR but passes with it.
  • Please, don't make changes to pnpm-lock.yaml unless you introduce a new test example.
  • Please check Allow edits by maintainers to make review process faster. Note that this option is not available for repositories that are owned by Github organizations.

Tests

  • Run the tests with pnpm test:ci.

Documentation

  • If you introduce new functionality, document it. You can run documentation with pnpm run docs command.

Changesets

  • Changes in changelog are generated from PR name. Please, make sure that it explains your changes in an understandable manner. Please, prefix changeset messages with feat:, fix:, perf:, docs:, or chore:.

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.

@netlify

netlify Bot commented Jul 27, 2026

Copy link
Copy Markdown

Deploy Preview for vitest-dev ready!

Built without sensitive environment variables

Name Link
🔨 Latest commit 2a20935
🔍 Latest deploy log https://app.netlify.com/projects/vitest-dev/deploys/6a75cf5b44b2f700070f1af8
😎 Deploy Preview https://deploy-preview-10843--vitest-dev.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
🤖 Make changes Run an agent on this branch

To edit notification comments on pull requests, go to your Netlify project configuration.

@emerson-d-lopes emerson-d-lopes changed the title fix: keep the drive-letter case Vitest was loaded with when externalizing imports fix: resolve Vitest's dist directory case-insensitively on Windows so only one runtime instance loads Jul 27, 2026
Emerson Lopes and others added 3 commits August 7, 2026 09:27
…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
emerson-d-lopes force-pushed the fix/windows-drive-letter-vitest-externalize branch from 5f68fb4 to 2a20935 Compare August 7, 2026 12:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

"Vitest failed to find the current suite" on Windows when using lowercase drive letter

1 participant