Describe the bug
When a custom test environment's setup() rejects, the pool wraps the failure as
[vitest-pool]: Failed to start <pool> worker for test files … and attaches the
original value via { cause }. However, the reporter only prints the cause when it
is an Error instance:
throw new Error("…") → report includes Caused by: Error: … ✅
throw "…" / throw { … } / a wasm-bindgen JsValue → the cause is silently dropped; the user sees only the generic, location-free pool message ❌
In Vitest 3.x the same non-Error throw was surfaced directly (Unknown Error: <value>),
so this is a regression. It appears to have come in with the worker-start error handling
added in #9337 / v4.0.17 (which correctly fixed the indefinite hang from #9271, but the
new wrapper only renders Error-typed causes).
This bites real-world environments that initialize native/wasm resources in setup()
and reject with a non-Error value. For example @stacks/clarinet-sdk's
vitest-environment-clarinet calls a wasm-bindgen initSimnet() in setup(); when a
contract fails to compile, the wasm rejects with a JsValue, and on v4 the actual
diagnostic is completely lost — you only see Failed to start forks worker.
Reproduction
https://stackblitz.com/edit/vitest-dev-vitest-ldzxobzz?file=throwing-env.js
package.json
{
"name": "vitest-env-setup-error-swallowed",
"version": "1.0.0",
"type": "module",
"private": true,
"scripts": {
"test": "vitest run"
},
"devDependencies": {
"vitest": "^4.1.8"
}
}
throwing-env.js
// Minimal custom test environment whose async setup() rejects with a NON-Error value.
// Real-world analogue: wasm-bindgen / native addons reject with a plain JsValue
// (e.g. @stacks/clarinet-sdk's simnet init), not an `Error` instance.
export default {
name: "throwing-env",
transformMode: "ssr",
async setup() {
// A proper `throw new Error("...")` IS surfaced by vitest (as `Caused by:`).
// A non-Error value is silently dropped — only the generic pool error remains.
throw "the real reason setup failed (a non-Error value)";
},
};
vitest.config.js
import { defineConfig } from "vitest/config";
export default defineConfig({
test: {
environment: "./throwing-env.js",
// pool: "forks" is the default; the message mentions "forks worker".
},
});
test/example.test.js
import { test, expect } from "vitest"; test("noop", () => { expect(1).toBe(1); });
Run npm install and npm test.
(Swap the throw for throw new Error("…") to see the cause correctly printed — that
contrast shows the wrapping/cause machinery is present; only non-Error rendering is
missing.)
System Info
System:
OS: macOS 26.5.1
CPU: (14) arm64 Apple M4 Pro
Memory: 120.22 MB / 48.00 GB
Shell: 5.9 - /bin/zsh
Binaries:
Node: 25.1.0 - /Users/brice/.nvm/versions/node/v25.1.0/bin/node
npm: 11.6.2 - /Users/brice/.nvm/versions/node/v25.1.0/bin/npm
Browsers:
Chrome: 149.0.7827.55
Safari: 26.5
npmPackages:
vitest: ^4.1.8 => 4.1.8
Used Package Manager
npm
Validations
Describe the bug
When a custom test environment's
setup()rejects, the pool wraps the failure as[vitest-pool]: Failed to start <pool> worker for test files …and attaches theoriginal value via
{ cause }. However, the reporter only prints the cause when itis an
Errorinstance:throw new Error("…")→ report includesCaused by: Error: …✅throw "…"/throw { … }/ a wasm-bindgenJsValue→ the cause is silently dropped; the user sees only the generic, location-free pool message ❌In Vitest 3.x the same non-
Errorthrow was surfaced directly (Unknown Error: <value>),so this is a regression. It appears to have come in with the worker-start error handling
added in #9337 / v4.0.17 (which correctly fixed the indefinite hang from #9271, but the
new wrapper only renders
Error-typed causes).This bites real-world environments that initialize native/wasm resources in
setup()and reject with a non-
Errorvalue. For example@stacks/clarinet-sdk'svitest-environment-clarinetcalls a wasm-bindgeninitSimnet()insetup(); when acontract fails to compile, the wasm rejects with a
JsValue, and on v4 the actualdiagnostic is completely lost — you only see
Failed to start forks worker.Reproduction
https://stackblitz.com/edit/vitest-dev-vitest-ldzxobzz?file=throwing-env.js
package.json{ "name": "vitest-env-setup-error-swallowed", "version": "1.0.0", "type": "module", "private": true, "scripts": { "test": "vitest run" }, "devDependencies": { "vitest": "^4.1.8" } }throwing-env.jsvitest.config.jstest/example.test.jsRun
npm installandnpm test.(Swap the
throwforthrow new Error("…")to see the cause correctly printed — thatcontrast shows the wrapping/
causemachinery is present; only non-Errorrendering ismissing.)
System Info
System: OS: macOS 26.5.1 CPU: (14) arm64 Apple M4 Pro Memory: 120.22 MB / 48.00 GB Shell: 5.9 - /bin/zsh Binaries: Node: 25.1.0 - /Users/brice/.nvm/versions/node/v25.1.0/bin/node npm: 11.6.2 - /Users/brice/.nvm/versions/node/v25.1.0/bin/npm Browsers: Chrome: 149.0.7827.55 Safari: 26.5 npmPackages: vitest: ^4.1.8 => 4.1.8Used Package Manager
npm
Validations