Describe the bug
When sass-embedded is used, one transient failure to spawn its compiler child process permanently breaks all scss compilation for the lifetime of the dev server — every subsequent scss transform fails with the same error (in our app: Pre-transform error: [sass] spawn EBADF repeated for every scss module, ~240 times) until the server is restarted.
Two things combine into the permanent failure:
initAsyncCompiler() resolves before the child-process spawn settles, so it never rejects — a failed spawn only surfaces later, as a rejection of each compileStringAsync() call against the dead process.
makeScssWorker caches the compiler forever (compilerPromise ??= sass.initAsyncCompiler() in packages/vite/src/node/plugins/css.ts) and never disposes/re-creates it on failure, so the dead compiler is handed to every subsequent compile.
The trigger in our case is an intermittent macOS posix_spawn EBADF under file-descriptor churn — a dev server for a large app holds ~10k fds, and cold dep-optimizer runs at startup reliably created the churn window (the failure correlated 1:1 with invalidated optimizer caches across our runs; warm caches never hit it). The trigger is environmental and racy, but the amplification — never respawning a dead compiler — is deterministic and visible in the source.
This is the same failure reported in #20825 (closed as unplanned, conversation locked), which is why I'm opening a fresh issue: we root-caused it and have a fix PR.
Reproduction
https://github.com/vitejs/vite/blob/main/packages/vite/src/node/plugins/css.ts
Steps to reproduce
The spawn race itself is environmental (macOS, high fd pressure), so there is no minimal repro that fails on demand — but the bricking behavior can be shown by making the first spawn fail by any means:
- Any project with an scss file and
sass-embedded installed.
- Make
sass.initAsyncCompiler()'s underlying child_process.spawn fail once (e.g. intercept and throw a spawn EBADF-shaped error, or reproduce organically on macOS by starting a dev server with a large module graph and a cold optimizer cache).
- Every scss request now fails with the same spawn error; only a server restart recovers. Expected: Vite drops the dead compiler and retries with a fresh process.
Field reports of the same signature: #20825, plus multiple reports in our organization on macOS 15/26 with Node 22–24.
System Info
System:
OS: macOS 15.x / 26.x (Apple Silicon)
Binaries:
Node: 22.x – 24.6.0 (reproduced across all)
npmPackages:
vite: 8.1.5 (also present on main)
sass-embedded: 1.100.0
Used Package Manager
yarn
Logs
2:57:04 PM [vite] (client) Pre-transform error: [sass] spawn EBADF
Plugin: vite:css
File: /Users/…/apps/agent-interface/src/app/utils/theme/theme.scss (x240)
(the (x240) is one failure per scss module in the graph — a single dead compiler, replayed forever)
Validations
Describe the bug
When
sass-embeddedis used, one transient failure to spawn its compiler child process permanently breaks all scss compilation for the lifetime of the dev server — every subsequent scss transform fails with the same error (in our app:Pre-transform error: [sass] spawn EBADFrepeated for every scss module, ~240 times) until the server is restarted.Two things combine into the permanent failure:
initAsyncCompiler()resolves before the child-process spawn settles, so it never rejects — a failed spawn only surfaces later, as a rejection of eachcompileStringAsync()call against the dead process.makeScssWorkercaches the compiler forever (compilerPromise ??= sass.initAsyncCompiler()inpackages/vite/src/node/plugins/css.ts) and never disposes/re-creates it on failure, so the dead compiler is handed to every subsequent compile.The trigger in our case is an intermittent macOS
posix_spawnEBADF under file-descriptor churn — a dev server for a large app holds ~10k fds, and cold dep-optimizer runs at startup reliably created the churn window (the failure correlated 1:1 with invalidated optimizer caches across our runs; warm caches never hit it). The trigger is environmental and racy, but the amplification — never respawning a dead compiler — is deterministic and visible in the source.This is the same failure reported in #20825 (closed as unplanned, conversation locked), which is why I'm opening a fresh issue: we root-caused it and have a fix PR.
Reproduction
https://github.com/vitejs/vite/blob/main/packages/vite/src/node/plugins/css.ts
Steps to reproduce
The spawn race itself is environmental (macOS, high fd pressure), so there is no minimal repro that fails on demand — but the bricking behavior can be shown by making the first spawn fail by any means:
sass-embeddedinstalled.sass.initAsyncCompiler()'s underlyingchild_process.spawnfail once (e.g. intercept and throw aspawn EBADF-shaped error, or reproduce organically on macOS by starting a dev server with a large module graph and a cold optimizer cache).Field reports of the same signature: #20825, plus multiple reports in our organization on macOS 15/26 with Node 22–24.
System Info
Used Package Manager
yarn
Logs
(the
(x240)is one failure per scss module in the graph — a single dead compiler, replayed forever)Validations