Mocha --exit: Decide Whether to Force Node.js Process Exit After Tests
Use natural Mocha exit to surface leaks; add --exit only when open handles block CI and cleanup is impractical.
18 Jul 2025, 11:01 UTC

The decision and constraints
You run your Mocha suite and the final test passes, but the terminal does not come back. In CI, the job stays alive until it times out. This usually happens because something kept the Node.js event loop running after Mocha finished executing tests.
The useful takeaway is to run Mocha without --exit during development so open handles surface as hangs. Add --exit only when a handle genuinely cannot be closed or when CI must return immediately. Treat it as a workaround, not a substitute for cleanup.
Constraints: this guidance applies to the Mocha command-line runner in Node.js. Mocha's programmatic API does not expose this exact CLI flag. Behavior can differ between Mocha major versions, so confirm the installed version with npx mocha --version before relying on --exit.
Supported options compared
| Option | What happens | Best for | Main risk |
|---|---|---|---|
No --exit | Mocha returns when the Node.js event loop is empty. If handles remain open, the process hangs. | Local development and leak detection | Indefinite CI hang if a server, socket, timer, or database client was left open |
--exit | Mocha calls process.exit() after the runner finishes, ending pending async work immediately. | CI pipelines that must return, or third-party code you cannot change | Can mask resource leaks and abort after hooks or cleanup scheduled after the runner completes |
Trade-offs
Natural exit is the stricter signal. If the process lingers, you know a handle is still active: an HTTP server listening on a port, a database pool, a file watcher, or a forgotten setInterval. That hang is a bug report waiting to happen.
--exit guarantees that the shell receives an exit code, which is valuable in CI. The cost is opacity: pending asynchronous work is terminated without running, so you may never discover that a test left a server running or that an after hook did not finish.
Implementation
Run from your project directory in a terminal. No special permissions are required.
npx mocha --exit --timeout 10000 'test/**/*.spec.js'
Replace 'test/**/*.spec.js' with the glob pattern that matches your test files. --timeout 10000 sets the per-test timeout in milliseconds; adjust or omit it based on your suite.
To use the flag in npm scripts, add it to the test command in package.json. Example script value:
mocha --exit --reporter dot 'test/**/*.spec.js'
Risk: because process.exit() stops the event loop immediately, any asynchronous cleanup queued after Mocha finishes may not run.
Validation before you enable --exit
- Run
npx mocha --versionand note the major version. Check that version's documentation for--exitsemantics. - Run the suite without the flag:
npx mocha 'test/**/*.spec.js'. The expected result is that the prompt returns after the reporter prints the summary. - If the process lingers, create a minimal reproduction: open an HTTP server or start a
setIntervalin a test and never close it. Confirm the process hangs without--exitand returns with it. Do not leave that reproduction in your committed suite. - Use Node.js diagnostics such as
node --trace-warningsor active-handle inspection to identify what is keeping the loop alive before you decide the flag is permanent.
Limitations
--exitmay abortafterhooks or custom process cleanup that runs after the test runner completes but before the event loop would naturally drain.- It does not close HTTP servers, database pools, or file watchers for you; it only terminates the process.
- Mocha version behavior for
--exitcan vary. Confirm against your installed major version rather than assuming identical semantics across releases.
Practical check and rollback
Adding --exit changes configuration, not system state. Rollback is removing the flag from your npm script or CI command.
After the change, run the suite locally and in CI. The shell should return immediately with an exit code that reflects whether tests passed or failed. If tests pass but the exit code is nonzero, or if the process still hangs, revisit unclosed handles instead of relying on the flag.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.