What happens
With DevTools() in a shared Vite config, running Vitest in multiple workspace packages in parallel (e.g. vp test -r / vitest -r) crashes all but one process:
Error: listen EADDRINUSE: address already in use ::1:7812
Single-package runs pass but still print: Tests closed successfully but something prevents Vite server from exiting — the DevTools WS server stays alive after the test run.
Root cause
DevToolsServer uses apply: 'serve' and starts the middleware in configureServer (packages/core/src/node/plugins/server.ts:44). Vitest runs Vite with command: 'serve', so the hook fires during test runs.
- Under Vitest there is no listening Vite HTTP server, so
viteDevServer.httpServer is nullish → routeBound is false (packages/core/src/node/ws.ts:72-73) → the standalone path opens a dedicated WS port.
- That port defaults to
getPort({ port: 7812, random: true }) (packages/core/src/node/ws.ts:74). get-port-please probes 7812 first and returns it when free; the random fallback only kicks in when the probe fails. Parallel test processes probe before any of them binds (TOCTOU), all "win" 7812, and all but one crash on the real bind.
So a plugin intended for dev servers activates in the test pipeline and binds a well-known fixed port per process.
Repro
- Monorepo with two or more packages whose
vite.config includes DevTools().
- Run Vitest in all packages in parallel (e.g.
vitest run via a recursive task runner).
- All but one process crash with EADDRINUSE on
::1:7812.
Expected
One of:
- DevTools does not activate when there is no real dev server (e.g. guard on
config.server.middlewareMode and/or the VITEST env var), or
- the standalone WS port allocation is race-safe for parallel processes (bind port 0 instead of probe-then-bind on a fixed default), or
- a documented option to disable the WS server / set its port.
Workaround
Conditionally load the plugin outside test runs:
plugins: [...(process.env.VITEST === undefined ? [DevTools()] : []), /* ... */]
Verified: with this guard, parallel test runs across 3 packages pass reliably; without it they crash deterministically.
Environment
- @vitejs/devtools 0.4.12
- vitest 4.1.10 (via vite-plus 0.2.7)
- Node.js 24.19.0, Windows 11
What happens
With
DevTools()in a shared Vite config, running Vitest in multiple workspace packages in parallel (e.g.vp test -r/vitest -r) crashes all but one process:Single-package runs pass but still print:
Tests closed successfully but something prevents Vite server from exiting— the DevTools WS server stays alive after the test run.Root cause
DevToolsServerusesapply: 'serve'and starts the middleware inconfigureServer(packages/core/src/node/plugins/server.ts:44). Vitest runs Vite withcommand: 'serve', so the hook fires during test runs.viteDevServer.httpServeris nullish →routeBoundis false (packages/core/src/node/ws.ts:72-73) → the standalone path opens a dedicated WS port.getPort({ port: 7812, random: true })(packages/core/src/node/ws.ts:74). get-port-please probes 7812 first and returns it when free; the random fallback only kicks in when the probe fails. Parallel test processes probe before any of them binds (TOCTOU), all "win" 7812, and all but one crash on the real bind.So a plugin intended for dev servers activates in the test pipeline and binds a well-known fixed port per process.
Repro
vite.configincludesDevTools().vitest runvia a recursive task runner).::1:7812.Expected
One of:
config.server.middlewareModeand/or theVITESTenv var), orWorkaround
Conditionally load the plugin outside test runs:
Verified: with this guard, parallel test runs across 3 packages pass reliably; without it they crash deterministically.
Environment