wtfnode

A debugging utility that reveals exactly which open handles and timers are keeping a Node.js process from exiting.

Tool
npm
v0.10.1
601stars
ISC

Repository Health

Pre-computed score based on development activity, maintenance, community, maturity, and trend momentum.How we score it →
30/100Needs Attention
Development Activity0
Maintenance0
Community40
Maturity60
Momentum20

Technical Analysis

AI-assessed by reading the actual repository — architecture, code quality, innovation, and documentation.How we score it →
56/100Fair
Architecture55
Code Quality48
Innovation65
Learning Curve55

wtfnode is a diagnostic tool for a problem every Node.js developer eventually hits: a process that refuses to exit even after the application logic has finished. It hooks into Node’s internal handle-creating constructors — sockets, servers, timers, child processes — before any other module gets a chance to wrap them, then prints a readable breakdown of what is still open and where in the source it was created.

It can be invoked from the command line as a wrapper around an existing entry point (wtfnode yourscript.js) or required directly inside an application and triggered on demand via wtf.dump(). Because it patches low-level constructors like net.Server, dgram.Socket, and the timer functions, it needs to be loaded before any other code establishes the connections or timers it is meant to track.

What You Get

  • Handle-by-handle breakdown of open sockets, servers, timers, and child processes keeping the event loop alive
  • Source-mapped callsites showing exactly which line of application code created each lingering handle
  • A CLI wrapper (wtfnode) that runs a target script and dumps the report automatically on Ctrl+C
  • A programmatic wtf.dump() API for triggering the report from inside a running application at any point
  • Configurable logging hooks (setLogger/resetLoggers) to redirect output to a custom logger instead of the console
  • A watchdog proxy process that guarantees the target can still be force-killed with a second Ctrl+C even when a SIGINT handler blocks normal exit

Common Use Cases

  • Diagnosing a CI job or one-off script that never exits after its work is done
  • Tracking down a leaked database, Redis, or HTTP connection that keeps a service alive
  • Investigating why a containerized process ignores SIGTERM and needs SIGKILL
  • Auditing a third-party dependency suspected of not closing its own resources

Under The Hood

Architecture wtfnode is a single monolithic module (index.js, roughly 900 lines) that monkey-patches Node’s core handle-creating constructors — net.Server, net.Socket, dgram.Socket, http.Server, https.Server, tls.Server, the timer functions, and child_process.ChildProcess — at require time, tagging each created instance with its originating callsite. A companion CLI entry point, proxy.js, forks the target script as a child process so it can watch for SIGINT and force-kill the child on a second Ctrl+C, working around Node’s behavior of disabling forced-exit-on-infinite-loop once a SIGINT handler is registered. The exported dump() function walks the instrumented constructors’ live instances, cross-references Node’s own active-handle bookkeeping, and renders a grouped, source-mapped tree. Because the whole mechanism depends on prototype patching of private Node internals with only a handful of NODE_VERSION branches to paper over differences, there is no abstraction layer isolating it from changes to those internals — a change to Node’s handle model is the single point of fragility for the entire reporting path.

Tech Stack The package is plain CommonJS JavaScript written in an ES5 style (var declarations, no classes), with zero runtime dependencies — coffeescript and source-map-support appear only as devDependencies used for sourcemap-related tests. It ships no build step, bundler, or TypeScript layer, distributing index.js and proxy.js directly via the files field. The bin field wires proxy.js up as the globally installable wtfnode command, while require('wtfnode') exposes the same functionality as a library for in-process use. The engines field claims support from Node 0.10 onward, and the code contains explicit version branches to accommodate that range.

Code Quality Tests are plain Node scripts driven by a hand-rolled shell runner (tests/tests.sh), using the built-in assert module rather than a test framework, and exercising a fairly broad set of real scenarios — event emitters, promises, util.promisify, HTTP clients and servers, cluster workers, the REPL, and both working and broken source-map setups. There is no CI workflow configuration in the repository, so these tests appear to run only locally or on publish rather than being enforced automatically. There is no linter or formatter configuration, no TypeScript types, and error handling is minimal and defensive (guard checks and best-effort fallbacks) rather than typed or exception-based, consistent with a module that intentionally reaches into private runtime internals.

What Makes It Unique The distinguishing technique is patching Node’s handle-creating constructors before any other module has a chance to, so that every open socket, timer, or child process can be traced back to the exact application callsite that created it — turning process._getActiveHandles(), which is technically available but low-level and hard to interpret, into a readable, attributable report. The companion watchdog-proxy CLI, which forks the target script and escalates to a forced kill on a second Ctrl+C, solves a specific and easy-to-miss Node quirk: registering a SIGINT handler otherwise prevents forceful termination of a process stuck in an infinite loop. Similar diagnostic tools exist in the ecosystem, so the approach is a solid, narrowly-scoped execution of a known technique rather than something without precedent.

Join founders buildingwith open source

Opinionated takes, migration guides, cost-saving tips, and insights from the open source ecosystem.

Subscribe on Substack
Join 750+ subscribers

Search