# why-is-node-running: diagnosing Node.js processes that stay alive

> why-is-node-running is a Node.js debugging package for finding active handles and timers that prevent a process from exiting. Its README limits it to development and short-lived debugging because importing it starts tracking immediately and adds ongoing memory and CPU cost.

**mafintosh/why-is-node-running** — Node is running but you don't know why? why-is-node-running is here to help you.

- Repository: https://github.com/mafintosh/why-is-node-running
- Stars: 1,948 · Forks: 52
- Language: JavaScript
- License: MIT
- Published: 2026-10-16 · Updated: 2026-10-16 · Language: en
- Canonical page: https://hysenlabs.com/projects/mafintosh-why-is-node-running

## What problem why-is-node-running addresses

Node.js applications can remain alive after the code that started them appears to have finished. Timers, servers and other active handles can keep the event loop occupied, but the application may not make it obvious which resources are responsible. why-is-node-running addresses that diagnosis by printing the active handles that are keeping the process running, along with stack locations that provide context for each one.

The package is intended for developers working on tests, local runs and short-lived debugging sessions. It is not presented as a production observability library. The README specifically says to use it in development or test code and to remove import statements after debugging. It also distinguishes importing the package from calling the diagnostic function: loading the module starts tracking immediately, so delaying the function call does not remove the tracking cost.

This is a narrow tool rather than a general Node.js profiler. The documented report focuses on active handles, including examples involving a repeating timer and TCP server handles. The package does not claim to explain all causes of a stalled application, such as blocked synchronous code, and the README does not document profiling CPU time or tracing individual asynchronous operations.

## The reporting mechanism and development-only boundary

The central design choice is that instrumentation begins when the module is loaded. The dependency workflow imports whyIsNodeRunning before the rest of the application, because the README says this ordering helps stacks include the right context. Later, calling whyIsNodeRunning() prints a report. That report contains a count of handles followed by grouped handle types and the locations where they were created or invoked.

The documented example creates two servers and two intervals. Its output reports four handles: two Timeout entries for the intervals and two TCPSERVERWRAP entries for the servers. Each group includes an application line such as example.js:6 or example.js:7 and a call-site line such as example.js:10 or example.js:11. That output gives a developer a path from the surviving resource back to the code that created it.

The cost boundary matters. The README says tracking adds memory and CPU overhead for as long as the process runs, and that the cost grows as the application becomes busier. The package should therefore not be left in production application code or a long-running service. A flag around the call is not enough if the import is still evaluated, because the module has already started tracking by then. For an existing application that must not be edited, the CLI and --import approaches avoid adding a permanent import to the source.

## Installing and running the first handle report

For an ECMAScript modules project using Node.js 20.11 or above, install the package as a development dependency. The documented command is:

```bash
npm install --save-dev why-is-node-running
```

Create a small file with the package as its first import, then register a handle that should be visible in the report. The README's example uses a server and an interval:

```js
import whyIsNodeRunning from 'why-is-node-running'
import { createServer } from 'node:net'

function startServer () {
  const server = createServer()
  setInterval(() => {}, 1000)
  server.listen(0)
}

startServer()
startServer()

setImmediate(() => whyIsNodeRunning())
```

Save it as example.js and run it with the documented Node.js command:

```bash
node ./example.js
```

The expected output starts with a statement that four handles are keeping the process running. It then lists the two Timeout entries and the two TCPSERVERWRAP entries with the relevant stack locations. This example is useful because it demonstrates the report, but it is not a benchmark or a claim about a particular application. The package is a diagnostic aid, so the output should be interpreted against the handles present in the process being investigated.

For CommonJS projects, the README gives a separate installation path for Node.js 8 or higher:

```bash
npm install --save-dev why-is-node-running@v2.x
```

The current package metadata requires Node.js >=20.11, so the version choice matters for projects using older Node.js releases.

## CLI diagnostics for an unchanged application

The CLI is the preferred documented workflow when a developer does not want to modify application source. It accepts the target JavaScript file, starts it under the package, and prints a probing message. The README shows this installation and invocation pattern:

```bash
npm install --global why-is-node-running
why-is-node-running /path/to/some/file.js
```

If global installation is not wanted, the README also documents running the command through npx:

```bash
npx why-is-node-running /path/to/some/file.js
```

The CLI output includes the process identifier and the signal needed to request logging. In the documented example, the output tells the user to run `kill -SIGUSR1 31115` after probing `/path/to/some/file.js`. The process ID is an example value and will differ for a real run. The README also states that sending `SIGUSR1` or `SIGINFO` produces the log, with `Ctrl + T` identified as the macOS and BSD shortcut.

This approach is useful when the application is already available as a file and the investigation should end without editing that file. It still requires the process to be running long enough for the signal to be sent. The README does not document a timeout, log destination or automatic shutdown command for the CLI, so those details should not be assumed.

## Preloading with Node.js --import

Node.js applications can also preload the package through its `include` export. The documented command is:

```bash
node --import why-is-node-running/include /path/to/some/file.js
```

The README says that the remaining steps are the same as the CLI section. That means the process is started with tracking available before the target file runs, and a signal can be used to request the report. Unlike the dependency example, this does not require adding an import statement to `/path/to/some/file.js`.

The `--import` workflow is a better fit than permanent source instrumentation when the diagnosis is temporary. It also avoids the question of where to place a diagnostic call inside the application. However, it still loads instrumentation into the process, so the README's warning remains relevant. It should be removed from launch commands and deployment settings after the investigation rather than left enabled for an ordinary long-lived process.

The package exports `.` as its main entry point and `./include` as the preloading entry point. The CLI is registered as `why-is-node-running` through the package's `bin` field. Those package boundaries explain why the README uses different invocations for dependency use, direct CLI use and `--import` use.

## Limitations, alternatives and upgrade considerations

The main limitation is stated directly by the project: tracking starts on import and consumes memory and CPU for the life of the process. The overhead increases as the application becomes busier. The package is therefore a poor fit for production application code, long-running services or any process where continuous instrumentation is unacceptable. The README also warns that placing `whyIsNodeRunning()` behind a flag does not solve this if the module import is still evaluated.

Its scope is also narrower than a full diagnostic platform. The documented output identifies active handles and stack context. It does not document CPU profiling, heap inspection, tracing every promise, detecting blocked JavaScript execution, or automatically terminating the process. If those are the questions being investigated, this package does not provide the answer on its own.

A practical alternative is to debug the application with Node.js's built-in facilities and inspect the relevant timers, servers, signals and test teardown paths directly, without importing this package. That approach avoids package overhead but requires the developer to identify and inspect resources manually. The difference is straightforward: why-is-node-running packages a report of active handles with creation context, while the alternative relies on application code and Node.js diagnostics outside this package.

The repository is MIT licensed. The repository is not archived, and the last push was on 2026-10-09. The recent release entries include v3.2.3 on 2026-10-09, v3.2.2 on 2025-01-08 and v3.2.1 on 2024-10-29. The current package metadata requires Node.js >=20.11, while the README retains an older CommonJS installation path using v2.x for Node.js 8 or higher. Upgrading across those lines should therefore include checking the Node.js runtime and whether the application uses ECMAScript modules or CommonJS. The README does not document a migration procedure, so it cannot support a more specific upgrade claim.

## Conclusion

Adopt why-is-node-running when a Node.js process should exit but remains active and you need its active handles and stack context. Do not import it into a production service or long-running application. Verify first that your Node.js version matches the package requirements, then choose the CLI or --import workflow to avoid leaving instrumentation in application code.

## FAQ

### How do I install why-is-node-running for an ECMAScript modules project?

For Node.js 20.11 and above, the README documents installing it as a development dependency with `npm install --save-dev why-is-node-running`. The current package metadata sets the Node.js engine requirement to >=20.11.

### How can I diagnose a Node.js process without changing its source code?

Use the CLI or preload the `include` entry point. The documented commands are `why-is-node-running /path/to/some/file.js` or `node --import why-is-node-running/include /path/to/some/file.js`, followed by the signal command shown by the process.

### Does importing why-is-node-running slow down a production application?

The README says loading the module starts tracking immediately and adds memory and CPU overhead for as long as the process runs. It directs users to use the package only for tests, local runs and short-lived debugging, not production application code or long-running services.

## Sources

- [Issues](https://github.com/mafintosh/why-is-node-running/issues)
- [License: MIT](https://github.com/mafintosh/why-is-node-running/blob/master/LICENSE)
- [mafintosh/why-is-node-running on GitHub](https://github.com/mafintosh/why-is-node-running)
- [README](https://github.com/mafintosh/why-is-node-running/blob/master/README.md)
- [Releases](https://github.com/mafintosh/why-is-node-running/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/mafintosh-why-is-node-running
