Title: @angular/build:unit-test vitest executor hangs indefinitely — uses close() instead of exit() · Issue #32832 · angular/angular-cli · GitHub
Open Graph Title: @angular/build:unit-test vitest executor hangs indefinitely — uses close() instead of exit() · Issue #32832 · angular/angular-cli
X Title: @angular/build:unit-test vitest executor hangs indefinitely — uses close() instead of exit() · Issue #32832 · angular/angular-cli
Description: Which @angular/* package(s) are the source of the bug? @angular/build Is this a regression? No — the vitest runner has always used close() since it was introduced. Description When running tests with the @angular/build:unit-test builder ...
Open Graph Description: Which @angular/* package(s) are the source of the bug? @angular/build Is this a regression? No — the vitest runner has always used close() since it was introduced. Description When running tests wi...
X Description: Which @angular/* package(s) are the source of the bug? @angular/build Is this a regression? No — the vitest runner has always used close() since it was introduced. Description When running tests wi...
Opengraph URL: https://github.com/angular/angular-cli/issues/32832
X: @github
Domain: github.com
{"@context":"https://schema.org","@type":"DiscussionForumPosting","headline":"@angular/build:unit-test vitest executor hangs indefinitely — uses close() instead of exit()","articleBody":"## Which @angular/* package(s) are the source of the bug?\n\n@angular/build\n\n## Is this a regression?\n\nNo — the vitest runner has always used `close()` since it was introduced.\n\n## Description\n\nWhen running tests with the `@angular/build:unit-test` builder using the vitest runner in non-watch mode, the process hangs indefinitely after all tests complete and results are printed. This happens because the builder calls `vitest.close()` which has no teardown timeout, while the vitest CLI calls `vitest.exit()` which has a safety-net `setTimeout → process.exit()`.\n\n### Root Cause\n\nThe vitest CLI (`vitest run`) handles shutdown like this (`node_modules/vitest/dist/chunks/cac.CWGDZnXT.js:2317`):\n\n```js\nconst ctx = await startVitest(mode, cliFilters, options);\nif (!ctx.shouldKeepServer()) await ctx.exit(); // ← has teardownTimeout safety net\n```\n\n`ctx.exit()` sets an unref'd `setTimeout(() =\u003e process.exit(), config.teardownTimeout)` before calling `close()`. If pool workers fail to shut down, `process.exit()` fires after 10s (default `teardownTimeout`).\n\nThe Angular builder's `VitestExecutor` (`executor.js:189`) uses `close()` directly:\n\n```js\nasync [Symbol.asyncDispose]() {\n await this.vitest?.close();\n}\n```\n\nAdditionally, `startVitest()` itself calls `ctx.close()` in its `finally` block (`cli-api.DuT9iuvY.js:14400-14402`):\n\n```js\nfinally {\n if (!ctx?.shouldKeepServer()) {\n await ctx.close(); // hangs here — no timeout\n }\n}\n```\n\n`close()` awaits `pool.close()` with no timeout. If any vitest pool worker doesn't respond to the \"stop\" message within 60s (`STOP_TIMEOUT`, hardcoded in vitest), `PoolRunner.stop()` throws but **never calls `this.worker.stop()`** to kill the process. The zombie workers' IPC channels (`ref=true`) keep the Node.js event loop alive indefinitely.\n\nThis is a known class of bugs in Vitest 4's pool implementation (vitest-dev/vitest#8766, vitest-dev/vitest#9494, vitest-dev/vitest#8861).\n\n### Suggested Fix\n\nAfter `startVitest()` returns, call `ctx.exit()` for non-watch mode, matching what the vitest CLI does:\n\n```js\nconst ctx = await startVitest('test', undefined, vitestConfig, vitestServerConfig);\nif (!ctx?.config?.watch) {\n await ctx.exit();\n}\n```\n\nOr add an equivalent teardown timeout in the `[Symbol.asyncDispose]()` method.\n\n## Please provide a link to a minimal reproduction of the bug\n\nNo external reproduction — the issue is observable from source code analysis:\n- `executor.js:189` calls `this.vitest?.close()` (no timeout)\n- `executor.js:314` calls `startVitest()` which calls `ctx.close()` in its finally block (no timeout)\n- Vitest CLI (`cac.CWGDZnXT.js:2317`) calls `ctx.exit()` (has `teardownTimeout` safety net)\n\nReproducible with any large Angular project (232+ test files) on Windows using `@angular/build:unit-test` with vitest runner and `--no-watch`.\n\n## Please provide the exception or error you saw\n\nNo error. The process completes all tests, prints the summary, then hangs indefinitely. `process._getActiveHandles()` shows 4 `ChildProcess` + 12 `Socket` handles from vitest forks pool workers that were never killed.\n\n## Please provide the environment you discovered this bug in\n\n```\n@angular/build: 21.2.2\nvitest: 4.1.0\nNode.js: 22.14.0\nOS: Windows 11 Pro 10.0.26200\n```\n\n## Anything else?\n\n**Workaround:** Add a `globalSetup` teardown in `vitest-base.config.ts` that mirrors `ctx.exit()`'s safety net:\n\n```ts\n// vitest-global-teardown.ts\nexport async function teardown() {\n setTimeout(() =\u003e process.exit(process.exitCode ?? 0), 10_000).unref();\n}\n```\n\nThe `_teardownGlobalSetup()` runs inside `Vitest.close()` before `pool.close()`, so the timer fires during the pool hang.","author":{"url":"https://github.com/scottcurrie","@type":"Person","name":"scottcurrie"},"datePublished":"2026-03-25T06:28:36.000Z","interactionStatistic":{"@type":"InteractionCounter","interactionType":"https://schema.org/CommentAction","userInteractionCount":3},"url":"https://github.com/32832/angular-cli/issues/32832"}
| route-pattern | /_view_fragments/issues/show/:user_id/:repository/:id/issue_layout(.:format) |
| route-controller | voltron_issues_fragments |
| route-action | issue_layout |
| fetch-nonce | v2:e57d423e-fe5e-7677-242a-8ba3ab3d2a32 |
| current-catalog-service-hash | 81bb79d38c15960b92d99bca9288a9108c7a47b18f2423d0f6438c5b7bcd2114 |
| request-id | E9F0:D62B7:304A41:45D326:6A636A87 |
| html-safe-nonce | bd51adf12a56ba4edc6beef577517da6977087f03a1cf29765a99d32180cb14e |
| visitor-payload | eyJyZWZlcnJlciI6IiIsInJlcXVlc3RfaWQiOiJFOUYwOkQ2MkI3OjMwNEE0MTo0NUQzMjY6NkE2MzZBODciLCJ2aXNpdG9yX2lkIjoiNzEzMzM3OTEwOTE3MTk4MDkzNSIsInJlZ2lvbl9lZGdlIjoiaWFkIiwicmVnaW9uX3JlbmRlciI6ImlhZCJ9 |
| visitor-hmac | 484a22dfec68b431fce053ac0dcb6d59fa787c8bf08a262732ef3d0824e42b9d |
| hovercard-subject-tag | issue:4132947783 |
| github-keyboard-shortcuts | repository,issues,copilot |
| google-site-verification | Apib7-x98H0j5cPqHWwSMm6dNU4GmODRoqxLiDzdx9I |
| octolytics-url | https://collector.github.com/github/collect |
| analytics-location | / |
| fb:app_id | 1401488693436528 |
| apple-itunes-app | app-id=1477376905, app-argument=https://github.com/_view_fragments/issues/show/angular/angular-cli/32832/issue_layout |
| twitter:image | https://opengraph.githubassets.com/07b4a6ce3ae6bbe0a82f919252fc519722eadfbb487a2cfd6ab50d862ea42690/angular/angular-cli/issues/32832 |
| twitter:card | summary_large_image |
| og:image | https://opengraph.githubassets.com/07b4a6ce3ae6bbe0a82f919252fc519722eadfbb487a2cfd6ab50d862ea42690/angular/angular-cli/issues/32832 |
| og:image:alt | Which @angular/* package(s) are the source of the bug? @angular/build Is this a regression? No — the vitest runner has always used close() since it was introduced. Description When running tests wi... |
| og:image:width | 1200 |
| og:image:height | 600 |
| og:site_name | GitHub |
| og:type | object |
| og:author:username | scottcurrie |
| hostname | github.com |
| expected-hostname | github.com |
| None | e7b5617850fbcd6d10fe2b7c56398444bccb5f3a58a4a18c6d363971295bcd03 |
| turbo-cache-control | no-preview |
| go-import | github.com/angular/angular-cli git https://github.com/angular/angular-cli.git |
| octolytics-dimension-user_id | 139426 |
| octolytics-dimension-user_login | angular |
| octolytics-dimension-repository_id | 36891867 |
| octolytics-dimension-repository_nwo | angular/angular-cli |
| octolytics-dimension-repository_public | true |
| octolytics-dimension-repository_is_fork | false |
| octolytics-dimension-repository_network_root_id | 36891867 |
| octolytics-dimension-repository_network_root_nwo | angular/angular-cli |
| turbo-body-classes | logged-out env-production page-responsive |
| disable-turbo | false |
| browser-stats-url | https://api.github.com/_private/browser/stats |
| browser-errors-url | https://api.github.com/_private/browser/errors |
| release | aa341b3f432dc63fa9633042725aec601722a352 |
| ui-target | canary-1 |
| theme-color | #1e2327 |
| color-scheme | light dark |
Links:
Viewport: width=device-width