## TL;DR

A Jest worker finished its tests but something, an open server, DB pool, timer, or subscription, kept the process alive, so Jest killed it. Close those handles in `afterAll`. Run with `--detectOpenHandles` to find the culprit. Reach for `--forceExit` only as a last resort in CI, since it hides the leak.

## Error

```text
A worker process has failed to exit gracefully and has been force exited. This could be caused by tests leaking due to improper teardown.
```

## Steps

1. Re-run with `--detectOpenHandles`: `npx jest --detectOpenHandles`. Expected: Jest prints the open handle (TCP server, timer, etc.) and the file holding it.
2. Close the handle in `afterAll`: stop servers, disconnect DB pools, clear intervals, unsubscribe listeners. Expected: the process exits on its own.
3. If a third-party library holds the handle, call its shutdown or disconnect method in teardown, or mock the library in tests. Expected: no leaked handles from dependencies.
4. For timers you intentionally leave running, call `.unref()` on them so they do not hold the process open. Expected: the worker exits while the timer logic stays testable.
5. Only if cleanup is genuinely impossible (a native module with no close API), add `--forceExit` to CI. Expected: the run ends, but treat the leak as tech debt, not a fix.

## When to use

- The exact "failed to exit gracefully and has been force exited" warning appears.
- Jest hangs for seconds after tests complete before exiting.
- CI runs time out in the teardown phase.

## When not to use

- Tests themselves fail; fix the failures first, teardown warnings are secondary.
- The message is "worker encountered child process exceptions" (workers crashing, not lingering).
- You already use `--forceExit`; remove it and fix the leak instead of hiding it.

## Tool compatibility

- Jest 27 through 30.
- `--detectOpenHandles` and `--forceExit` CLI flags.
- Applies to node and jsdom environments.

## Variant phrasings

### jest worker process failed to exit gracefully

Same warning. The worker finished its tests but the Node process would not exit.

### jest force exited worker

Jest killed the lingering worker. Find the leaked handle rather than accepting the kill.

### jest hangs after tests complete

Often the same cause: open handles keeping the process alive past the last test.

## Why it happens

Jest runs each test file in a worker process and expects the process to exit when done. Open handles (listening servers, DB connections, intervals, sockets) keep the Node event loop alive, so the worker never exits and Jest force-kills it after a grace period.

## Edge cases

- `--detectOpenHandles` slows the run and can report handles from Jest internals; focus on handles your code created.
- Closing a server stops new connections, but keep-alive connections can still hold it open; destroy sockets or set short timeouts too.
- `--forceExit` can cut off async teardown and corrupt shared fixtures; prefer real cleanup.

## Provenance

Resolved from the public thread: https://vectle.com/posts/pst_2cX-iCRunW91Eq_7t004bQ
