Problem
DomStack.stopWatching() calls Chokidar's asynchronous close() without awaiting its result.
It also disposes the esbuild context without explicitly waiting for the queued rebuilds to finish.
The shutdown promise therefore does not establish that all watcher and rebuild work has completed before callers remove fixtures or restart watching.
Watch startup also acquires resources in stages, so failures after acquiring an esbuild context or copy watcher need a reliable cleanup path.
Evidence and uncertainty
This was inspected on master at a522e60.
The installed Chokidar declaration returns Promise<void> from close(), and its documentation calls for awaiting closure.
A local Node test run on macOS with Node 24.20.0 passed 70 of 76 tests, with six watch cases failing with EMFILE.
The run occurred in a restricted execution environment.
The connection between those errors and the missing await has not been established, and this issue should not assume that awaiting closure alone fixes EMFILE.
Chokidar performs some closure work synchronously, so the resource failure needs independent verification if it persists.
Proposed fix
- Define a shutdown order that stops new events, awaits Chokidar closure, closes copy watchers according to their actual contract, drains or safely cancels queued rebuilds, and then disposes esbuild and the development server.
- Ensure a failure during one cleanup step does not prevent cleanup of other acquired resources.
- Release partially acquired resources when watch startup fails.
- Add focused lifecycle regression tests, including stopping while a rebuild is pending and repeated watch/stop cycles.
- Recheck the local watch failures outside the restricted runner before attributing them to a library resource leak.
Acceptance criteria
- Awaiting
stopWatching() means DOMStack has completed its pending work and released its owned watch resources.
- No DOMStack-triggered rebuild or output write occurs after shutdown resolves.
- A failed startup releases resources it acquired.
- Repeated watch/stop cycles and existing granular watch tests pass.
- Test cleanup stops watchers before removing source fixtures.
Sequencing
Related to #294, which changes watch dependency invalidation but leaves this shutdown code unchanged at fedccb6.
A narrowly scoped teardown correction can accompany #294 if it is validated independently.
Broader lifecycle work can follow without blocking the dependency correctness fixes.
Problem
DomStack.stopWatching()calls Chokidar's asynchronousclose()without awaiting its result.It also disposes the esbuild context without explicitly waiting for the queued rebuilds to finish.
The shutdown promise therefore does not establish that all watcher and rebuild work has completed before callers remove fixtures or restart watching.
Watch startup also acquires resources in stages, so failures after acquiring an esbuild context or copy watcher need a reliable cleanup path.
Evidence and uncertainty
This was inspected on master at
a522e60.The installed Chokidar declaration returns
Promise<void>fromclose(), and its documentation calls for awaiting closure.A local Node test run on macOS with Node 24.20.0 passed 70 of 76 tests, with six watch cases failing with
EMFILE.The run occurred in a restricted execution environment.
The connection between those errors and the missing await has not been established, and this issue should not assume that awaiting closure alone fixes
EMFILE.Chokidar performs some closure work synchronously, so the resource failure needs independent verification if it persists.
Proposed fix
Acceptance criteria
stopWatching()means DOMStack has completed its pending work and released its owned watch resources.Sequencing
Related to #294, which changes watch dependency invalidation but leaves this shutdown code unchanged at
fedccb6.A narrowly scoped teardown correction can accompany #294 if it is validated independently.
Broader lifecycle work can follow without blocking the dependency correctness fixes.