Describe the bug
The pnpm global-virtual-store fs.allow detection added in #22415 (shipped in [email protected]) does not fire for nested workspace packages in a pnpm monorepo.
In resolveServerOptions, Vite reads .modules.yaml from searchForPackageRoot(root):
const cwd = searchForPackageRoot(root);
const pnpmModulesYaml = path.join(cwd, "node_modules", ".modules.yaml");
try {
const content = fs.readFileSync(pnpmModulesYaml, "utf-8");
const virtualStoreDir = JSON.parse(content).virtualStoreDir;
// ...push virtualStoreDir onto allowDirs
} catch {}
searchForPackageRoot returns the nearest dir containing a package.json. In a pnpm workspace the Vite project root is itself a workspace package, so cwd becomes that package (e.g. packages/app) — but .modules.yaml only exists at the monorepo root node_modules. The read ENOENTs into the empty catch {}, so virtualStoreDir is never added to server.fs.allow.
This only bites with enableGlobalVirtualStore: true, where deps symlink into a store outside the workspace (e.g. ~/.local/share/pnpm/store). Without GVS the virtual store sits under <repo>/node_modules/.pnpm, already covered by searchForWorkspaceRoot(root), so #22415 isn't needed there. With GVS it is needed and doesn't fire → the dev server returns 403 over /@fs for every dependency.
Suggested fix: resolve .modules.yaml from the workspace root rather than the package root — e.g. read it from searchForWorkspaceRoot(root), or walk up from cwd to the first ancestor whose node_modules/.modules.yaml exists, before giving up.
Reproduction
No hosted link — this is a filesystem / pnpm-store-location bug and isn't reproducible on StackBlitz / vite.new (WebContainers don't model a pnpm store outside the workspace). A complete, self-contained minimal repro is inlined under Steps to reproduce below; it's three small files and runs with pnpm install && pnpm --filter app repro. Happy to push it to a repo if that's preferred.
Steps to reproduce
Create this workspace (pnpm with enableGlobalVirtualStore: true, one nested package):
pnpm-workspace.yaml
enableGlobalVirtualStore: true
packages:
- 'packages/*'
package.json
{ "name": "repro", "private": true, "version": "0.0.0" }
packages/app/package.json
{
"name": "app",
"private": true,
"type": "module",
"scripts": { "repro": "node repro.mjs" },
"devDependencies": { "vite": "^8.1.0" }
}
packages/app/repro.mjs
import fs from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import { resolveConfig, searchForWorkspaceRoot, version } from 'vite';
// searchForPackageRoot is not exported; inline it to show what
// resolveServerOptions uses as cwd for the .modules.yaml lookup.
function searchForPackageRoot(current, root = current) {
if (fs.existsSync(path.join(current, 'package.json'))) return current;
const dir = path.dirname(current);
if (!dir || dir === current) return root;
return searchForPackageRoot(dir, root);
}
const root = path.dirname(fileURLToPath(import.meta.url));
const config = await resolveConfig({ root, configFile: false, logLevel: 'silent' }, 'serve');
const viteReal = fs.realpathSync(path.join(root, 'node_modules', 'vite'));
console.log('vite version: ', version);
console.log('searchForPackageRoot(root): ', searchForPackageRoot(root), '(Vite reads .modules.yaml here)');
console.log('searchForWorkspaceRoot(root):', searchForWorkspaceRoot(root), '(where .modules.yaml lives)');
console.log('a dep resolves to: ', viteReal);
console.log('resolved server.fs.allow: ', config.server.fs.allow);
const covered = config.server.fs.allow.some((d) => viteReal.startsWith(d));
console.log('\n' + (covered ? 'PASS' : 'BUG: deps are OUTSIDE fs.allow -> dev server 403s over /@fs'));
Then:
pnpm install
pnpm --filter app repro
Observed output ([email protected], macOS, pnpm 11.7.0):
vite version: 8.1.0
searchForPackageRoot(root): /.../repro/packages/app (Vite reads .modules.yaml here)
searchForWorkspaceRoot(root): /.../repro (where .modules.yaml lives)
a dep resolves to: /Users/me/.local/share/pnpm/store/v11/links/@/vite/8.1.0/.../node_modules/vite
resolved server.fs.allow: [
'/.../repro',
'/Users/me/.local/share/pnpm/store/v11/links/@/vite/8.1.0/.../node_modules/vite/dist/client'
]
BUG: deps are OUTSIDE fs.allow -> dev server 403s over /@fs
The root .modules.yaml has virtualStoreDir: ../../../...Users/me/.local/share/pnpm/store/v11/links, but it's never read because Vite looks in packages/app/node_modules/.modules.yaml, which doesn't exist.
System Info
System:
OS: macOS 26.5.1
CPU: (11) arm64 Apple M3 Pro
Binaries:
Node: 24.16.0
pnpm: 11.7.0
npmPackages:
vite: 8.1.0
Used Package Manager
pnpm
Validations
Describe the bug
The pnpm global-virtual-store
fs.allowdetection added in #22415 (shipped in[email protected]) does not fire for nested workspace packages in a pnpm monorepo.In
resolveServerOptions, Vite reads.modules.yamlfromsearchForPackageRoot(root):searchForPackageRootreturns the nearest dir containing apackage.json. In a pnpm workspace the Vite project root is itself a workspace package, socwdbecomes that package (e.g.packages/app) — but.modules.yamlonly exists at the monorepo rootnode_modules. The readENOENTs into the emptycatch {}, sovirtualStoreDiris never added toserver.fs.allow.This only bites with
enableGlobalVirtualStore: true, where deps symlink into a store outside the workspace (e.g.~/.local/share/pnpm/store). Without GVS the virtual store sits under<repo>/node_modules/.pnpm, already covered bysearchForWorkspaceRoot(root), so #22415 isn't needed there. With GVS it is needed and doesn't fire → the dev server returns 403 over/@fsfor every dependency.Suggested fix: resolve
.modules.yamlfrom the workspace root rather than the package root — e.g. read it fromsearchForWorkspaceRoot(root), or walk up fromcwdto the first ancestor whosenode_modules/.modules.yamlexists, before giving up.Reproduction
No hosted link — this is a filesystem / pnpm-store-location bug and isn't reproducible on StackBlitz /
vite.new(WebContainers don't model a pnpm store outside the workspace). A complete, self-contained minimal repro is inlined under Steps to reproduce below; it's three small files and runs withpnpm install && pnpm --filter app repro. Happy to push it to a repo if that's preferred.Steps to reproduce
Create this workspace (pnpm with
enableGlobalVirtualStore: true, one nested package):pnpm-workspace.yamlpackage.json{ "name": "repro", "private": true, "version": "0.0.0" }packages/app/package.json{ "name": "app", "private": true, "type": "module", "scripts": { "repro": "node repro.mjs" }, "devDependencies": { "vite": "^8.1.0" } }packages/app/repro.mjsThen:
Observed output (
[email protected], macOS, pnpm 11.7.0):The root
.modules.yamlhasvirtualStoreDir: ../../../...Users/me/.local/share/pnpm/store/v11/links, but it's never read because Vite looks inpackages/app/node_modules/.modules.yaml, which doesn't exist.System Info
Used Package Manager
pnpm
Validations