Skip to content

fix(workbench): exit the dev process after signal-triggered teardown#1290

Draft
gu-stav wants to merge 2 commits into
fix/workbench-rebuild-registryfrom
fix/workbench-dev-shutdown
Draft

fix(workbench): exit the dev process after signal-triggered teardown#1290
gu-stav wants to merge 2 commits into
fix/workbench-rebuild-registryfrom
fix/workbench-dev-shutdown

Conversation

@gu-stav

@gu-stav gu-stav commented Jun 12, 2026

Copy link
Copy Markdown
Member

Description

Closing sanity dev very often leaves the dev server running in the background, still holding its ports. Root cause: the workbench signal handler traps SIGINT/SIGTERM (which disables Node's default exit) and runs the teardown, but never exits. Any handle that survives teardown — keep-alive sockets, an extraction worker mid-run, a mid-rebuild server — keeps the event loop alive forever. Under a wrapper (pnpm, turbo, Nix supervisors) the wrapper dies on the same signal and returns the prompt while the node process lingers detached, so a second Ctrl-C never reaches it.

The handler now re-raises the signal once teardown settles — the once handler is gone by then, so the default termination runs with conventional signal exit semantics. An unref'd 5s timer covers a teardown that never settles, without itself keeping the process alive.

Two shutdown races fixed alongside, since the forced exit would otherwise just paper over them:

  • A close() racing an interface-set rebuild only saw the already-torn-down old server — the replacement booted afterwards with nothing left to close it. close() now waits for the rebuild in flight, and a rebuild requested after shutdown has started is refused.
  • SIGINT followed by SIGTERM (each signal keeps its own once handler), or a signal racing the caller's own close(), ran two concurrent teardowns. close() is single-flight now.

A hung manifest-extraction worker (no timeout, runs user config under jsdom) was the third hole found in the investigation — deliberately left alone here, since the re-raise kills it along with the process.

Stacked on #1289 (touches the same onInterfaceSetChange path); retarget to feat/workbench once that merges.

What to review

All in devAction.ts: the onSignal re-raise + grace timer, the memoized close(), and the closed guard in runRebuild (checked before the first await, so a rebuild can't slip past a close() that already read rebuildInFlight).

Testing

devAction.test.ts covers the re-raise after teardown (with ordering), the force-exit on a hung close, close() waiting for an in-flight rebuild, single-flight close, and the refused-rebuild-during-shutdown path.

@github-actions

github-actions Bot commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

📦 Bundle Stats — @sanity/cli

Compared against fix/workbench-rebuild-registry (724a139e)

@sanity/cli

Metric Value vs fix/workbench-rebuild-registry (724a139)
Internal (raw) 2.7 KB -
Internal (gzip) 1.1 KB -
Bundled (raw) 11.15 MB -
Bundled (gzip) 2.10 MB -
Import time 862ms -12ms, -1.4%

bin:sanity

Metric Value vs fix/workbench-rebuild-registry (724a139)
Internal (raw) 782 B -
Internal (gzip) 423 B -
Bundled (raw) 9.87 MB -
Bundled (gzip) 1.77 MB -
Import time 2.01s -36ms, -1.8%

🗺️ View treemap · Artifacts

Details
  • Import time regressions over 10% are flagged with ⚠️
  • Sizes shown as raw / gzip 🗜️. Internal bytes = own code only. Total bytes = with all dependencies. Import time = Node.js cold-start median.

📦 Bundle Stats — @sanity/cli-core

Compared against fix/workbench-rebuild-registry (724a139e)

Metric Value vs fix/workbench-rebuild-registry (724a139)
Internal (raw) 105.5 KB -
Internal (gzip) 25.8 KB -
Bundled (raw) 21.71 MB -
Bundled (gzip) 3.45 MB -
Import time 775ms -8ms, -1.0%

🗺️ View treemap · Artifacts

Details
  • Import time regressions over 10% are flagged with ⚠️
  • Sizes shown as raw / gzip 🗜️. Internal bytes = own code only. Total bytes = with all dependencies. Import time = Node.js cold-start median.

📦 Bundle Stats — create-sanity

Compared against fix/workbench-rebuild-registry (724a139e)

Metric Value vs fix/workbench-rebuild-registry (724a139)
Internal (raw) 908 B -
Internal (gzip) 483 B -
Bundled (raw) 931 B -
Bundled (gzip) 491 B -
Import time ❌ ChildProcess denied: node -
Details
  • Import time regressions over 10% are flagged with ⚠️
  • Sizes shown as raw / gzip 🗜️. Internal bytes = own code only. Total bytes = with all dependencies. Import time = Node.js cold-start median.

@github-actions

Copy link
Copy Markdown
Contributor

Preview this PR with pkg.pr.new

Run the Sanity CLI

npx https://pkg.pr.new/sanity-io/cli/@sanity/cli@42b61f2 <command>

...Or upgrade project dependencies

📦 @sanity/cli
pnpm install https://pkg.pr.new/@sanity/cli@42b61f2
📦 @sanity/cli-build
pnpm install https://pkg.pr.new/@sanity/cli-build@42b61f2
📦 @sanity/cli-core
pnpm install https://pkg.pr.new/@sanity/cli-core@42b61f2
📦 @sanity/cli-test
pnpm install https://pkg.pr.new/@sanity/cli-test@42b61f2
📦 @sanity/eslint-config-cli
pnpm install https://pkg.pr.new/@sanity/eslint-config-cli@42b61f2

View Commit (42b61f2)

@github-actions

Copy link
Copy Markdown
Contributor

Coverage Delta

File Statements
packages/@sanity/cli-build/src/actions/build/getEntryModule.ts 100.0% (±0%)
packages/@sanity/cli-build/src/actions/build/getViteConfig.ts 100.0% (±0%)
packages/@sanity/cli-build/src/actions/build/writeSanityRuntime.ts 96.2% (+ 0.7%)
packages/@sanity/cli-build/src/workbench/artifact.ts 60.0% (new)
packages/@sanity/cli-build/src/workbench/contract.ts 100.0% (new)
packages/@sanity/cli-build/src/workbench/defineApp.ts 100.0% (new)
packages/@sanity/cli-build/src/workbench/defineService.ts 100.0% (new)
packages/@sanity/cli-build/src/workbench/defineView.ts 100.0% (new)
packages/@sanity/cli-build/src/workbench/render-remote.ts 100.0% (new)
packages/@sanity/cli-build/src/workbench/services/artifact.ts 100.0% (new)
packages/@sanity/cli-build/src/workbench/views/artifact.ts 100.0% (new)
packages/@sanity/cli-build/src/workbench/vite/constants.ts 100.0% (new)
packages/@sanity/cli-build/src/workbench/vite/plugin.ts 90.0% (new)
packages/@sanity/cli-build/src/workbench/vite/plugins/plugin-module-federation.ts 92.8% (new)
packages/@sanity/cli-build/src/workbench/vite/plugins/plugin-sanity-environment.ts 100.0% (new)
packages/@sanity/cli-build/src/workbench/vite/plugins/plugin-sanity-extension-artifacts.ts 100.0% (new)
packages/@sanity/cli-build/src/workbench/vite/plugins/plugin-sanity-federation-runtime.ts 34.3% (new)
packages/@sanity/cli-build/src/workbench/vite/utils/invariant.ts 50.0% (new)
packages/@sanity/cli-core/src/config/cli/getCliConfig.ts 100.0% (±0%)
packages/@sanity/cli-core/src/config/cli/getCliConfigSync.ts 81.0% (+ 38.9%)
packages/@sanity/cli-core/src/config/cli/workbenchApp.ts 100.0% (new)
packages/@sanity/cli-core/src/services/cliUserConfig.ts 100.0% (±0%)
packages/@sanity/cli-core/src/util/getSanityConfigDir.ts 66.7% (new)
packages/@sanity/cli/src/actions/build/buildApp.ts 95.4% (+ 0.1%)
packages/@sanity/cli/src/actions/build/buildStaticFiles.ts 97.7% (+ 0.9%)
packages/@sanity/cli/src/actions/build/buildStudio.ts 96.7% (+ 0.1%)
packages/@sanity/cli/src/actions/deploy/deployApp.ts 73.6% (- 7.0%)
packages/@sanity/cli/src/actions/deploy/deployStudio.ts 91.7% (- 0.8%)
packages/@sanity/cli/src/actions/deploy/viewDeployment.ts 66.7% (new)
packages/@sanity/cli/src/actions/deploy/workbenchChecks.ts 100.0% (new)
packages/@sanity/cli/src/actions/dev/devAction.ts 100.0% (±0%)
packages/@sanity/cli/src/actions/dev/registration/deriveInterfaces.ts 100.0% (new)
packages/@sanity/cli/src/actions/dev/registration/extractDevServerManifest.ts 20.0% (new)
packages/@sanity/cli/src/actions/dev/registration/interfaceSetId.ts 100.0% (new)
packages/@sanity/cli/src/actions/dev/registration/startDevManifestWatcher.ts 90.9% (new)
packages/@sanity/cli/src/actions/dev/registration/startDevServerRegistration.ts 100.0% (new)
packages/@sanity/cli/src/actions/dev/registry/processLiveness.ts 89.7% (new)
packages/@sanity/cli/src/actions/dev/registry/registry.ts 93.6% (new)
packages/@sanity/cli/src/actions/dev/registry/workbenchLock.ts 97.7% (new)
packages/@sanity/cli/src/actions/dev/servers/getDashboardAppUrl.ts 100.0% (new)
packages/@sanity/cli/src/actions/dev/servers/getDevServerConfig.ts 100.0% (new)
packages/@sanity/cli/src/actions/dev/servers/startAppDevServer.ts 100.0% (new)
packages/@sanity/cli/src/actions/dev/servers/startStudioDevServer.ts 100.0% (new)
packages/@sanity/cli/src/actions/dev/shared/canonicalizeWatchDir.ts 100.0% (new)
packages/@sanity/cli/src/actions/dev/workbench/startWorkbenchDevServer.ts 98.8% (new)
packages/@sanity/cli/src/actions/dev/workbench/writeWorkbenchRuntime.ts 100.0% (new)
packages/@sanity/cli/src/actions/init/bootstrapLocalTemplate.ts 89.7% (±0%)
packages/@sanity/cli/src/actions/init/bootstrapTemplate.ts 0.0% (±0%)
packages/@sanity/cli/src/actions/init/createAppCliConfig.ts 100.0% (±0%)
packages/@sanity/cli/src/actions/init/createCliConfig.ts 100.0% (+ 50.0%)
packages/@sanity/cli/src/actions/init/createStudioConfig.ts 83.3% (±0%)
packages/@sanity/cli/src/actions/init/initAction.ts 93.3% (+ 0.1%)
packages/@sanity/cli/src/actions/init/initApp.ts 96.0% (±0%)
packages/@sanity/cli/src/actions/init/initStudio.ts 87.0% (±0%)
packages/@sanity/cli/src/actions/init/scaffoldTemplate.ts 100.0% (±0%)
packages/@sanity/cli/src/actions/init/types.ts 100.0% (±0%)
packages/@sanity/cli/src/actions/manifest/extractCoreAppManifest.ts 93.9% (new)
packages/@sanity/cli/src/actions/manifest/extractManifest.ts 6.3% (+ 6.3%)
packages/@sanity/cli/src/actions/manifest/iconResolver.tsx 100.0% (±0%)
packages/@sanity/cli/src/actions/manifest/sanitizeIcon.ts 100.0% (new)
packages/@sanity/cli/src/actions/manifest/types.ts 100.0% (±0%)
packages/@sanity/cli/src/actions/manifest/writeManifestFile.ts 22.2% (+ 22.2%)
packages/@sanity/cli/src/commands/dev.ts 100.0% (±0%)
packages/@sanity/cli/src/commands/init.ts 100.0% (±0%)
packages/@sanity/cli/src/commands/manifest/extract.ts 0.0% (±0%)
packages/@sanity/cli/src/server/devServer.ts 94.4% (±0%)
packages/@sanity/cli/src/util/determineIsApp.ts 100.0% (±0%)
packages/@sanity/cli/src/util/getSharedServerConfig.ts 100.0% (±0%)
packages/@sanity/cli/src/util/resolveReactStrictMode.ts 100.0% (new)

Comparing 69 changed files against main @ fe61c5dd97f3e5c02352bace830d15fb0180cfcc

Overall Coverage

Metric Coverage
Statements 82.4% (+ 1.5%)
Branches 73.6% (+ 1.2%)
Functions 82.2% (+ 2.9%)
Lines 82.9% (+ 1.6%)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant