Node Inspect Debugger
Use when Node.js debugging requires hidden locals, async hangs, flaky tests, child processes, startup races, memory analysis, or CPU profiling.
Default to node inspect first. Use Chrome DevTools Protocol only for scripted breakpoints, automated state capture, heap snapshots, or CPU profiles.
Quick start
node inspect path/to/script.js
node --inspect-brk --import tsx path/to/script.ts
kill -SIGUSR1 <pid>
node inspect -p <pid>
curl -s http://127.0.0.1:9229/json/list | jq
For a test runner such as Vitest, debug one file with one worker and avoid worker pools while stepping.
Debugger REPL
- Continue and step:
cont, next, step, out, pause.
- Breakpoints:
sb('file.js', 42), sb(42), sb('functionName'), breakpoints, cb('file.js', 42).
- Inspect:
bt, list(8), watch('expr'), exec expr.
- Current scope: enter
repl, evaluate locals, then press Ctrl+C to exit REPL mode.
- Exit safely: use
cont before quitting if the process should keep running; otherwise use kill.
Safe setup
- Prefer
127.0.0.1 inspector binds. Do not expose --inspect=0.0.0.0 unless the network is isolated.
- Use
--enable-source-maps when TypeScript source breakpoints need it.
NODE_OPTIONS=--inspect-brk can propagate the inspector to child processes, but each process needs a unique port.
- For a long-lived service, confirm the PID and
/json/list target before attaching.
Programmatic CDP
Install temporary tooling outside the project unless it is already a dependency:
mkdir -p /tmp/cdp-tools
npm --prefix /tmp/cdp-tools install chrome-remote-interface
NODE_PATH=/tmp/cdp-tools/node_modules node /tmp/cdp-debug.cjs
Minimal driver:
const CDP = require("chrome-remote-interface");
(async () => {
const client = await CDP({ port: 9229 });
const { Debugger, Runtime } = client;
Debugger.paused(async ({ callFrames, reason }) => {
const top = callFrames[0];
console.log("paused", reason, top.url, top.location.lineNumber + 1);
const { result } = await Debugger.evaluateOnCallFrame({
callFrameId: top.callFrameId,
expression: "JSON.stringify({ pid: process.pid })",
});
console.log(result.value ?? result.description);
await Debugger.resume();
});
await Runtime.enable();
await Debugger.enable();
await Debugger.setBreakpointByUrl({ urlRegex: ".*target\\.js$", lineNumber: 41 });
await Runtime.runIfWaitingForDebugger();
})();
Profiles
- CPU: enable
Profiler, start, wait, stop, write a .cpuprofile, then open it in Chrome DevTools.
- Heap: enable
HeapProfiler, collect addHeapSnapshotChunk, call takeHeapSnapshot, then write a .heapsnapshot.
Pitfalls
--inspect does not pause; use --inspect-brk when startup code matters.
- Port
9229 is the default; use --inspect=0 or a unique port for parallel targets.
- If a breakpoint misses, confirm the path, source maps, and whether execution already passed the line.
- A process that appears frozen after detach may still be paused in the debugger.
1---2name: node-inspect-debugger3description: Debug Node.js with node inspect, inspector attach, breakpoints, Chrome DevTools Protocol, heap snapshots, and CPU profiles.4---56# Node Inspect Debugger78Use when Node.js debugging requires hidden locals, async hangs, flaky tests, child processes, startup races, memory analysis, or CPU profiling.910Default to `node inspect` first. Use Chrome DevTools Protocol only for scripted breakpoints, automated state capture, heap snapshots, or CPU profiles.1112## Quick start1314```bash15node inspect path/to/script.js16node --inspect-brk --import tsx path/to/script.ts17kill -SIGUSR1 <pid>18node inspect -p <pid>19curl -s http://127.0.0.1:9229/json/list | jq20```2122For a test runner such as Vitest, debug one file with one worker and avoid worker pools while stepping.2324## Debugger REPL2526- Continue and step: `cont`, `next`, `step`, `out`, `pause`.27- Breakpoints: `sb('file.js', 42)`, `sb(42)`, `sb('functionName')`, `breakpoints`, `cb('file.js', 42)`.28- Inspect: `bt`, `list(8)`, `watch('expr')`, `exec expr`.29- Current scope: enter `repl`, evaluate locals, then press `Ctrl+C` to exit REPL mode.30- Exit safely: use `cont` before quitting if the process should keep running; otherwise use `kill`.3132## Safe setup3334- Prefer `127.0.0.1` inspector binds. Do not expose `--inspect=0.0.0.0` unless the network is isolated.35- Use `--enable-source-maps` when TypeScript source breakpoints need it.36- `NODE_OPTIONS=--inspect-brk` can propagate the inspector to child processes, but each process needs a unique port.37- For a long-lived service, confirm the PID and `/json/list` target before attaching.3839## Programmatic CDP4041Install temporary tooling outside the project unless it is already a dependency:4243```bash44mkdir -p /tmp/cdp-tools45npm --prefix /tmp/cdp-tools install chrome-remote-interface46NODE_PATH=/tmp/cdp-tools/node_modules node /tmp/cdp-debug.cjs47```4849Minimal driver:5051```js52const CDP = require("chrome-remote-interface");5354(async () => {55 const client = await CDP({ port: 9229 });56 const { Debugger, Runtime } = client;5758 Debugger.paused(async ({ callFrames, reason }) => {59 const top = callFrames[0];60 console.log("paused", reason, top.url, top.location.lineNumber + 1);61 const { result } = await Debugger.evaluateOnCallFrame({62 callFrameId: top.callFrameId,63 expression: "JSON.stringify({ pid: process.pid })",64 });65 console.log(result.value ?? result.description);66 await Debugger.resume();67 });6869 await Runtime.enable();70 await Debugger.enable();71 await Debugger.setBreakpointByUrl({ urlRegex: ".*target\\.js$", lineNumber: 41 });72 await Runtime.runIfWaitingForDebugger();73})();74```7576## Profiles7778- CPU: enable `Profiler`, start, wait, stop, write a `.cpuprofile`, then open it in Chrome DevTools.79- Heap: enable `HeapProfiler`, collect `addHeapSnapshotChunk`, call `takeHeapSnapshot`, then write a `.heapsnapshot`.8081## Pitfalls8283- `--inspect` does not pause; use `--inspect-brk` when startup code matters.84- Port `9229` is the default; use `--inspect=0` or a unique port for parallel targets.85- If a breakpoint misses, confirm the path, source maps, and whether execution already passed the line.86- A process that appears frozen after detach may still be paused in the debugger.