Skip to content

vitest.rootConfig (and other path settings) don't resolve ${workspaceFolder} #778

@whme

Description

@whme

Describe the bug

The path-valued settings contributed by this extension don't consistently run VS Code's predefined-variable substitution before passing values to Vitest. Placeholders such as ${workspaceFolder} appear verbatim in the resolved path, and Vitest fails to load the config / binary.

Concretely, looking at packages/extension/src/config.ts:

  • vitest.vitestPackagePath does substitute ${workspaceFolder} (see config.ts:50).
  • vitest.rootConfig, vitest.workspaceConfig, vitest.nodeExecutable are all routed through resolveConfigPath() (config.ts:89-94, defined at config.ts:104-118), which handles absolute paths, ~/, and relative-to-workspace paths, but does not substitute ${workspaceFolder} (or any other VS Code variable). If the value starts with ${workspaceFolder}/…, isAbsolute() returns false, so resolve(workspaceFolder.fsPath, "${workspaceFolder}/…") is called and the literal placeholder survives in the output path.
  • vitest.terminalShellPath and vitest.debugOutFiles receive no substitution at all.

VS Code does not resolve these placeholders globally - per the Variables Reference, each extension must substitute them itself when it reads its configuration. Many extensions do (Python, ESLint, Ruff, Mypy, Rust-analyzer, …). That this extension handles ${workspaceFolder} for vitestPackagePath but not for rootConfig looks like an oversight rather than intentional behavior.

Expected behavior

${workspaceFolder} - and ideally the other commonly used predefined variables (${workspaceFolderBasename}, ${userHome}, ${env:NAME}, ${pathSeparator}, ${fileWorkspaceFolder}) - are expanded before the value is handed to Vitest, consistently across all path-valued settings (vitest.rootConfig, vitest.workspaceConfig, vitest.nodeExecutable, vitest.terminalShellPath, vitest.vitestPackagePath, vitest.debugOutFiles), matching what many other VS Code extensions already do.

For multi-root workspaces, ${workspaceFolder} should resolve to the folder that owns the setting (i.e. the folder scope of the WorkspaceConfiguration the value was read from), not always the first folder.

Reproduction

https://github.com/whme/vitest-vscode-workspaceFolder-repro

  1. pnpm install
  2. Open the folder in VS Code with the Vitest extension enabled.
  3. Open the Testing panel / "Vitest: Show Output Channel".
  4. Observe: the extension fails to load the config; the Output Channel shows the literal ${workspaceFolder} embedded in the resolved path.
  5. Replace the vitest.rootConfig value in .vscode/settings.json with an absolute path - tests are discovered correctly. This isolates variable substitution as the cause.

The config is deliberately placed under config/vitest.config.ts so the extension's default config search cannot find it - forcing the extension to rely on vitest.rootConfig, which is exactly the setting that fails.

Output

[INFO 2:54:51 PM] [v1.50.2] Vitest extension is activated because Vitest is installed or there is a Vite/Vitest config file in the workspace.
[INFO 2:54:51 PM] [API] Using user root config: <REDACTED>/vitest-vscode-workspaceFolder-repro/${workspaceFolder}/config/vitest.config.ts
[INFO 2:54:52 PM] [API] Resolving configs: config/vitest.config.ts
[2:54:52 PM] [API] Spawning on-demand process...
[INFO 2:54:52 PM] [API] Running Vitest v4.1.5 (config/vitest.config.ts) with "<REDACTED>/.nvm/versions/node/v22.21.1/bin/node <REDACTED>/.vscode/extensions/vitest.explorer-1.50.2/dist/worker.js"
[Error 2:54:52 PM] Current PATH: <REDACTED>
[Error 2:54:52 PM] There were errors during config load.
[Error 2:54:52 PM] [Error Error] spawn <REDACTED>/.nvm/versions/node/v22.21.1/bin/node ENOENT
Error: spawn <REDACTED>/.nvm/versions/node/v22.21.1/bin/node ENOENT
	at ChildProcess._handle.onexit (node:internal/child_process:285:19)
	at onErrorNT (node:internal/child_process:483:16)
	at process.processTicksAndRejections (node:internal/process/task_queues:89:21)
[Error 2:54:53 PM] [Error Error] The extension could not load any config.
Error: The extension could not load any config.
	at St (<REDACTED>/.vscode/extensions/vitest.explorer-1.50.2/dist/extension.js:1:70727)
	at process.processTicksAndRejections (node:internal/process/task_queues:103:5)
	at async $n._defineTestProfiles (<REDACTED>/.vscode/extensions/vitest.explorer-1.50.2/dist/extension.js:11:5119)
	at async $n.defineTestProfiles (<REDACTED>/.vscode/extensions/vitest.explorer-1.50.2/dist/extension.js:11:4437)
	at async $n.activate (<REDACTED>/.vscode/extensions/vitest.explorer-1.50.2/dist/extension.js:11:13144)
	at async Zn (<REDACTED>/.vscode/extensions/vitest.explorer-1.50.2/dist/extension.js:11:3089)
	at async Mb._activate (file:///usr/share/code/resources/app/out/vs/workbench/api/node/extensionHostProcess.js:501:15965)
	at async Mb._waitForDepsThenActivate (file:///usr/share/code/resources/app/out/vs/workbench/api/node/extensionHostProcess.js:501:15907)
	at async Mb._initialize (file:///usr/share/code/resources/app/out/vs/workbench/api/node/extensionHostProcess.js:501:15274)


> Note: in this failure mode the extension never successfully spawns Vitest, so the log doesn't contain a "Vitest version …" line - config loading dies first. The above is the full Output Channel content for the reproducer.

Extension Version

v1.50.2

Vitest Version

4.1.5

Validations

Metadata

Metadata

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions