Observed on run 34061414876 (PR #4878 CI, 2026-09-06): the desktop workspace reported 2357 pass / 0 fail / 1 cancelled, yet node --test never finished — scripts/vite-workspace-packages.test.mjs stayed pending for 811s until the workspace hit its 900s ceiling:
⚠ scripts/vite-workspace-packages.test.mjs
✔ renderer loads a newly exported workspace module after its manifest changes (198ms)
✔ renderer-facing Runtime Host protocol does not load Node crypto (44ms)
✖ scripts/vite-workspace-packages.test.mjs (811706ms)
'Promise resolution is still pending but the event loop has already resolved'
[desktop] timed out after 900000ms
Both subtests completed and reported; the child process did not exit. #4748 removed this failure mode for the second test by disabling its watcher, but the first test needs its watcher (it asserts restart-on-manifest-change), so it stays exposed.
Root cause (reproduced ~7% of iterations under a 3-way CPU/IO load on Linux, node 24, vite 8.2.2): workspacePackagesPlugin registers manifests as configFileDependencies, chokidar therefore also watches each manifest's parent directory; the manifest edit mid-test triggers a dev-server restart whose fresh watcher keeps scanning in the background; server.close() does not wait for (or cancel) that scan, and the vite-bundled, pnpm-patched chokidar 3.6.0 (the top-level chokidar 5.0.0 is not in this call chain) drops the closer of an fs.watch it already created: _handleDir() creates the directory watcher and returns its closer, but the caller _addToNodeFs() registers it with _addPathCloser() only after re-checking this.fsw.closed — when close() lands inside that await gap, it sweeps a still-empty _closers list and the resumed _addToNodeFs discards the closer at the closed check. An independent forced-interleave reproduction confirms the drop point (closed=true, closers empty, native fs.watch still open; invoking the lost closer by hand closes it). Chokidar 5.0.0 shares the same structure, so this is a chokidar bug, vendored (and patched) by vite. The orphan (FSEventWrap + pending FSReqCallback, confirmed via process.getActiveResourcesInfo() after close) keeps the child alive forever.
Proposed fix: --test-force-exit on the desktop test:dist runner, so tests that already completed report normally while lingering handles can no longer hold the workspace run hostage.
Observed on run 34061414876 (PR #4878 CI, 2026-09-06): the desktop workspace reported 2357 pass / 0 fail / 1 cancelled, yet
node --testnever finished —scripts/vite-workspace-packages.test.mjsstayed pending for 811s until the workspace hit its 900s ceiling:Both subtests completed and reported; the child process did not exit. #4748 removed this failure mode for the second test by disabling its watcher, but the first test needs its watcher (it asserts restart-on-manifest-change), so it stays exposed.
Root cause (reproduced ~7% of iterations under a 3-way CPU/IO load on Linux, node 24, vite 8.2.2):
workspacePackagesPluginregisters manifests asconfigFileDependencies, chokidar therefore also watches each manifest's parent directory; the manifest edit mid-test triggers a dev-server restart whose fresh watcher keeps scanning in the background;server.close()does not wait for (or cancel) that scan, and the vite-bundled, pnpm-patched chokidar 3.6.0 (the top-level chokidar 5.0.0 is not in this call chain) drops the closer of anfs.watchit already created:_handleDir()creates the directory watcher and returns its closer, but the caller_addToNodeFs()registers it with_addPathCloser()only after re-checkingthis.fsw.closed— whenclose()lands inside that await gap, it sweeps a still-empty_closerslist and the resumed_addToNodeFsdiscards the closer at the closed check. An independent forced-interleave reproduction confirms the drop point (closed=true, closers empty, nativefs.watchstill open; invoking the lost closer by hand closes it). Chokidar 5.0.0 shares the same structure, so this is a chokidar bug, vendored (and patched) by vite. The orphan (FSEventWrap+ pendingFSReqCallback, confirmed viaprocess.getActiveResourcesInfo()after close) keeps the child alive forever.Proposed fix:
--test-force-exiton the desktoptest:distrunner, so tests that already completed report normally while lingering handles can no longer hold the workspace run hostage.