BundledSoftware DevelopmentVersion 1.0.0

Node Inspect Debugger: Drive V8 Inspector from the Terminal

Debug Node.js via --inspect + Chrome DevTools Protocol CLI.

Written by Neura Market from the official Hermes Agent documentation for Node Inspect Debugger. Commands, paths, and version numbers are reproduced from the source unchanged.

Read the official documentation

When console.log stops telling you enough, the Node.js Inspect Debugger skill lets you drive the V8 inspector programmatically from the terminal. You get real breakpoints, step in/over/out, call-stack walking, local and closure scope dumps, and arbitrary expression evaluation in the paused frame. This skill is bundled with Hermes Agent and is the go-to tool for anyone debugging Node.js code, especially the Ink-based TUI, child processes, or Vitest tests.

What it does

This skill gives you two complementary tools for debugging Node.js:

  • node inspect, a built-in, zero-install CLI REPL. Best for quick poking at a running script or attaching to an already-running process.
  • Programmatic CDP via chrome-remote-interface, scriptable from Node or Python. Best when you want to automate many breakpoints, collect state across runs, or debug non-interactively from an agent loop.

The source recommends starting with node inspect first because it is always available and the REPL is fast.

Before you start

  • Node.js must be installed (version 12 or later recommended for full inspector support).
  • node inspect is built-in; no additional install needed.
  • chrome-remote-interface is an npm package. Install it globally or locally as needed. It is NOT in ui-tui/package.json, so install it to a throwaway location if you want to keep the project clean.
  • Platforms: linux, macos, windows (all supported).
  • Permissions: You need the ability to send signals (kill -SIGUSR1) to processes on the same machine, and network access to 127.0.0.1:9229 (or the custom port).
  • For Hermes TUI debugging: The TUI is built with Ink and tsx. You need the ui-tui directory built (npm run build) before debugging the compiled output.

Quick Reference: node inspect REPL

Launch a script paused on the first line:

node inspect path/to/script.js
# or with tsx
node --inspect-brk $(which tsx) path/to/script.ts

Once the script is paused, you get a debug> prompt. The following commands are available:

CommandAction
c or contcontinue
n or nextstep over
s or stepstep into
o or outstep out
pausepause running code
sb('file.js', 42)set breakpoint at file.js line 42
sb(42)set breakpoint at line 42 of current file
sb('functionName')break when function is called
cb('file.js', 42)clear breakpoint
breakpointslist all breakpoints
btbacktrace (call stack)
list(5)show 5 lines of source around current position
watch('expr')evaluate expr on every pause
watchersshow watched expressions
repldrop into REPL in current scope (Ctrl+C to exit REPL)
exec exprevaluate expression once
restartrestart script
killkill the script
.exitquit debugger

In the repl sub-mode: type any JS expression, including access to locals and closure variables. Ctrl+C exits back to debug>.

Attaching to a Running Process

When the process is already running (e.g. a long-lived dev server or the TUI gateway), you can enable the inspector on it and then attach:

# 1. Send SIGUSR1 to enable the inspector on an existing process
kill -SIGUSR1 <pid>
# Node prints: Debugger listening on ws://127.0.0.1:9229/<uuid>

# 2. Attach the debugger CLI
node inspect -p <pid>
# or by URL
node inspect ws://127.0.0.1:9229/<uuid>

To start a process with the inspector from the beginning:

node --inspect script.js           # listen on 127.0.0.1:9229, keep running
node --inspect-brk script.js       # listen AND pause on first line
node --inspect=0.0.0.0:9230 script.js   # custom host:port

For TypeScript via tsx:

node --inspect-brk --import tsx script.ts
# or older tsx
node --inspect-brk -r tsx/cjs script.ts

Programmatic CDP (scripting from terminal)

When you want to automate debugging, set many breakpoints, capture scope state, script a repro, use chrome-remote-interface:

npm i -g chrome-remote-interface        # or project-local
# Start your target:
node --inspect-brk=9229 target.js &

Driver script (save as /tmp/cdp-debug.js):

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}`);

    // Walk scopes for locals
    for (const scope of top.scopeChain) {
      if (scope.type === 'local' || scope.type === 'closure') {
        const { result } = await Runtime.getProperties({
          objectId: scope.object.objectId,
          ownProperties: true,
        });
        for (const p of result) {
          console.log(`  ${scope.type}.${p.name} =`, p.value?.value ?? p.value?.description);
        }
      }
    }

    // Evaluate an expression in the paused frame
    const { result } = await Debugger.evaluateOnCallFrame({
      callFrameId: top.callFrameId,
      expression: 'typeof state !== "undefined" ? JSON.stringify(state) : "n/a"',
    });
    console.log('state =', result.value ?? result.description);

    await Debugger.resume();
  });

  await Runtime.enable();
  await Debugger.enable();

  // Set a breakpoint by URL regex + line
  await Debugger.setBreakpointByUrl({
    urlRegex: '.*app\\.tsx$',
    lineNumber: 119,       // 0-indexed
    columnNumber: 0,
  });

  await Runtime.runIfWaitingForDebugger();
})();

Run it:

node /tmp/cdp-debug.js

Hermes-specific note: chrome-remote-interface is NOT in ui-tui/package.json. Install it to a throwaway location if you don't want to dirty the project:

mkdir -p /tmp/cdp-tools && cd /tmp/cdp-tools && npm i chrome-remote-interface
NODE_PATH=/tmp/cdp-tools/node_modules node /tmp/cdp-debug.js

Debugging Hermes ui-tui

The TUI is built with Ink and tsx. Two common scenarios:

Debugging a single Ink component under dev

ui-tui/package.json has npm run dev (tsx --watch). Add --inspect-brk by running tsx directly:

cd /home/bb/hermes-agent/ui-tui
npm run build    # produce dist/ once so transpile isn't needed on first load
node --inspect-brk dist/entry.js
# In another terminal:
node inspect -p <node pid>

Then inside debug>:

sb('dist/app.js', 220)     # or wherever the suspect render is
cont

When it pauses, repl → inspect props, state refs, useInput handler values, etc.

Debugging a running hermes --tui

The TUI spawns Node from the Python CLI. Easiest path:

# 1. Launch TUI
hermes --tui &
TUI_PID=$(pgrep -f 'ui-tui/dist/entry' | head -1)

# 2. Enable inspector on that Node PID
kill -SIGUSR1 "$TUI_PID"

# 3. Find the WS URL
curl -s http://127.0.0.1:9229/json/list | jq -r '.[0].webSocketDebuggerUrl'

# 4. Attach
node inspect ws://127.0.0.1:9229/<uuid>

Interacting with the TUI (typing in its window) continues to advance execution; your debugger can pause it on a breakpoint at any sb(...).

Debugging _SlashWorker / PTY child processes

Those are Python, not Node, use the python-debugpy skill for them. Only Node portions (Ink UI, tui_gateway client, tsx-run tests under ui-tui/) use this skill.

Running Vitest Tests Under the Debugger

cd /home/bb/hermes-agent/ui-tui
# Run a single test file paused on entry
node --inspect-brk ./node_modules/vitest/vitest.mjs run --no-file-parallelism src/app/foo.test.tsx

In another terminal: node inspect -p , then sb('src/app/foo.tsx', 42), cont.

Use --no-file-parallelism (vitest) or --runInBand (jest) so only one worker exists, debugging a pool is painful.

Heap Snapshots & CPU Profiles (Non-interactive)

From the CDP driver above, swap Debugger for HeapProfiler / Profiler:

// CPU profile for 5 seconds
await client.Profiler.enable();
await client.Profiler.start();
await new Promise(r => setTimeout(r, 5000));
const { profile } = await client.Profiler.stop();
require('fs').writeFileSync('/tmp/cpu.cpuprofile', JSON.stringify(profile));
// Open /tmp/cpu.cpuprofile in Chrome DevTools → Performance tab
// Heap snapshot
await client.HeapProfiler.enable();
const chunks = [];
client.HeapProfiler.addHeapSnapshotChunk(({ chunk }) => chunks.push(chunk));
await client.HeapProfiler.takeHeapSnapshot({ reportProgress: false });
require('fs').writeFileSync('/tmp/heap.heapsnapshot', chunks.join(''));

Common Pitfalls

  1. Wrong line numbers in TS source. Breakpoints hit the emitted JS, not the .ts. Either (a) break in the built dist/*.js, or (b) enable sourcemaps (node --enable-source-maps) and use sb('src/app.tsx', N), but only with CDP clients that follow sourcemaps. node inspect CLI does not.
  2. --inspect vs --inspect-brk. --inspect starts the inspector but doesn't pause; your script races past your first breakpoint if you attach too late. Use --inspect-brk when you need to set breakpoints before any code runs.
  3. Port collisions. Default is 9229. If multiple Node processes are inspecting, pass --inspect=0 (random port) and read the actual URL from /json/list:
curl -s http://127.0.0.1:9229/json/list   # lists all inspectable targets on the host
  1. Child processes. --inspect on a parent does NOT inspect its children. Use NODE_OPTIONS='--inspect-brk' node parent.js to propagate to every child; be aware they all need unique ports (Node auto-increments when NODE_OPTIONS='--inspect' is inherited).
  2. Background kills. If you Ctrl+C out of node inspect while the target is paused, the target stays paused. Either cont first, or kill the target explicitly.
  3. Running node inspect through an agent terminal. It's a PTY-friendly REPL. In Hermes, launch it with terminal(pty=true) or background=true + process(action='submit', data='...'). Non-PTY foreground mode will work for one-shot commands but not for interactive stepping.
  4. Security. --inspect=0.0.0.0:9229 exposes arbitrary code execution. Always bind to 127.0.0.1 (the default) unless you have an isolated network.

Verification Checklist

After setting up a debug session, verify:

  • curl -s http://127.0.0.1:9229/json/list returns exactly the target you expect
  • First breakpoint actually hits (if it doesn't, you likely missed --inspect-brk or attached after execution completed)
  • Source listing at pause shows the right file (mismatch = sourcemap issue, see pitfall 1)
  • exec process.pid in repl returns the PID you meant to attach to

One-Shot Recipes

"Why is this variable undefined at line X?"

node --inspect-brk script.js &
node inspect -p $!
# debug>
sb('script.js', X)
cont
# paused. Now:
repl
> myVariable
> Object.keys(this)

"What's the call path into this function?"

debug> sb('suspectFn')
debug> cont
# paused on entry
debug> bt

"This async chain hangs, where?"

# Start with --inspect (no -brk), let it run to the hang, then:
debug> pause
debug> bt
# Now you see the stuck frame

When not to use it

Don't use this skill for things console.log solves in under a minute. Breakpoint-driven debugging is heavier; use it when the payoff is real. Also, this skill is for Node.js debugging only. For Python child processes (_SlashWorker, PTY bridge workers), use the python-debugpy skill instead.

Limits and gotchas

  • The node inspect CLI does not follow sourcemaps. Breakpoints in TypeScript source files will not hit as expected unless you break on the compiled JavaScript output.
  • --inspect on a parent process does not automatically inspect child processes. You must use NODE_OPTIONS to propagate the flag, and each child needs a unique port.
  • If you exit the debugger while the target is paused, the target stays paused. Always resume or kill the target explicitly.
  • The default inspector port is 9229. If multiple processes are inspecting, you will get port collisions unless you use --inspect=0 for a random port.
  • Binding the inspector to 0.0.0.0 exposes arbitrary code execution to the network. Always bind to 127.0.0.1 unless you are on an isolated network.

Related skills

  • systematic-debugging: A broader debugging methodology that pairs well with this skill.
  • python-debugpy: Use this for debugging Python child processes (_SlashWorker, PTY bridge workers) instead of the Node Inspect Debugger.

Skills the docs pair this with

More Software Development skills