node-inspect-debugger
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseNode.js Inspect Debugger
Node.js Inspect 调试器
Overview
概述
When isn't enough, drive Node's built-in V8 inspector programmatically from the terminal. You get real breakpoints, step in/over/out, call-stack walking, local/closure scope dumps, and arbitrary expression evaluation in the paused frame.
console.logTwo tools, pick one:
- — built-in, zero install, CLI REPL. Best for quick poking.
node inspect - / CDP via
ndb— scriptable from Node/Python; best when you want to automate many breakpoints, collect state across runs, or debug non-interactively from an agent loop.chrome-remote-interface
Prefer first. It's always available and the REPL is fast.
node inspect当 不足以排查问题时,可以通过终端以编程方式调用 Node 内置的 V8 调试器。你可以设置真实断点、单步进入/跳过/退出、遍历调用栈、查看局部/闭包作用域信息,以及在暂停的帧中执行任意表达式求值。
console.log提供两种工具,任选其一:
- —— 内置工具,无需额外安装,CLI 交互式解释器。适合快速调试。
node inspect - / 基于
ndb的 CDP —— 可通过 Node/Python 编写脚本;适合需要自动化设置多个断点、跨运行周期收集状态,或通过代理循环进行非交互式调试的场景。chrome-remote-interface
优先推荐使用 。它随时可用,且交互式解释器响应迅速。
node inspectWhen to Use
使用场景
- A Node test fails and you need to see intermediate state
- ui-tui crashes or behaves wrong and you want to inspect React/Ink state pre-render
- tui_gateway child processes (, PTY bridge workers) misbehave
_SlashWorker - You need to inspect a value in a closure that can't reach without patching
console.log - Perf: attach to a running process to capture a CPU profile or heap snapshot
Don't use for: things solves in under a minute. Breakpoint-driven debugging is heavier; use it when the payoff is real.
console.log- Node 测试用例失败,需要查看中间状态
- ui-tui 崩溃或行为异常,想要检查 React/Ink 渲染前的状态
- tui_gateway 子进程(、PTY 桥接工作进程)行为异常
_SlashWorker - 需要查看 无法触及的闭包中的值,且不想修改代码
console.log - 性能分析:附加到运行中的进程以捕获 CPU 分析报告或堆快照
不建议使用的场景: 能在一分钟内解决的问题。基于断点的调试开销更大,仅在能获得实际收益时使用。
console.logQuick Reference: node inspect
REPL
node inspectnode inspect
交互式解释器速查
node inspectLaunch paused on first line:
bash
node inspect path/to/script.js启动并在第一行暂停:
bash
node inspect path/to/script.jsor with tsx
或配合 tsx 使用
node --inspect-brk $(which tsx) path/to/script.ts
The `debug>` prompt accepts:
| Command | Action |
|---|---|
| `c` or `cont` | continue |
| `n` or `next` | step over |
| `s` or `step` | step into |
| `o` or `out` | step out |
| `pause` | pause 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 |
| `breakpoints` | list all breakpoints |
| `bt` | backtrace (call stack) |
| `list(5)` | show 5 lines of source around current position |
| `watch('expr')` | evaluate expr on every pause |
| `watchers` | show watched expressions |
| `repl` | drop into REPL in current scope (Ctrl+C to exit REPL) |
| `exec expr` | evaluate expression once |
| `restart` | restart script |
| `kill` | kill the script |
| `.exit` | quit debugger |
**In the `repl` sub-mode:** type any JS expression, including access to locals/closure variables. `Ctrl+C` exits back to `debug>`.node --inspect-brk $(which tsx) path/to/script.ts
`debug>` 提示符支持以下命令:
| 命令 | 操作 |
|---|---|
| `c` 或 `cont` | 继续执行 |
| `n` 或 `next` | 单步跳过 |
| `s` 或 `step` | 单步进入 |
| `o` 或 `out` | 单步退出 |
| `pause` | 暂停运行中的代码 |
| `sb('file.js', 42)` | 在 file.js 的第42行设置断点 |
| `sb(42)` | 在当前文件的第42行设置断点 |
| `sb('functionName')` | 调用指定函数时触发断点 |
| `cb('file.js', 42)` | 清除断点 |
| `breakpoints` | 列出所有断点 |
| `bt` | 回溯调用栈 |
| `list(5)` | 显示当前位置周围5行源代码 |
| `watch('expr')` | 每次暂停时计算指定表达式 |
| `watchers` | 显示所有监视的表达式 |
| `repl` | 进入当前作用域的交互式解释器(按 Ctrl+C 退出) |
| `exec expr` | 执行一次指定表达式 |
| `restart` | 重启脚本 |
| `kill` | 终止脚本 |
| `.exit` | 退出调试器 |
**在 `repl` 子模式下:** 可输入任意 JS 表达式,包括访问局部变量/闭包变量。按 `Ctrl+C` 返回 `debug>` 提示符。Attaching to a Running Process
附加到运行中的进程
When the process is already running (e.g. a long-lived dev server or the TUI gateway):
bash
undefined当进程已在运行时(例如长期运行的开发服务器或 TUI 网关):
bash
undefined1. Send SIGUSR1 to enable the inspector on an existing process
1. 发送 SIGUSR1 信号,为现有进程启用调试器
kill -SIGUSR1 <pid>
kill -SIGUSR1 <pid>
Node prints: Debugger listening on ws://127.0.0.1:9229/<uuid>
Node 会输出:Debugger listening on ws://127.0.0.1:9229/<uuid>
2. Attach the debugger CLI
2. 附加调试器 CLI
node inspect -p <pid>
node inspect -p <pid>
or by URL
或通过 URL 附加
node inspect ws://127.0.0.1:9229/<uuid>
To start a process with the inspector from the beginning:
```bash
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:portFor TypeScript via tsx:
bash
node --inspect-brk --import tsx script.tsnode inspect ws://127.0.0.1:9229/<uuid>
从启动时就启用调试器:
```bash
node --inspect script.js # 在 127.0.0.1:9229 监听,保持运行
node --inspect-brk script.js # 监听并在第一行暂停
node --inspect=0.0.0.0:9230 script.ts # 自定义主机:端口配合 tsx 调试 TypeScript:
bash
node --inspect-brk --import tsx script.tsor older tsx
或旧版本 tsx
node --inspect-brk -r tsx/cjs script.ts
undefinednode --inspect-brk -r tsx/cjs script.ts
undefinedProgrammatic CDP (scripting from terminal)
程序化 CDP(终端脚本调试)
When you want to automate — set many breakpoints, capture scope state, script a repro — use :
chrome-remote-interfacebash
npm i -g chrome-remote-interface # or project-local当需要自动化操作——设置多个断点、捕获作用域状态、编写复现脚本——时,使用 :
chrome-remote-interfacebash
npm i -g chrome-remote-interface # 或安装到项目本地Start your target:
启动目标进程:
node --inspect-brk=9229 target.js &
Driver script (save as `/tmp/cdp-debug.js`):
```javascript
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:
bash
node /tmp/cdp-debug.jsHermes-specific note: is NOT in . Install it to a throwaway location if you don't want to dirty the project:
chrome-remote-interfaceui-tui/package.jsonbash
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.jsnode --inspect-brk=9229 target.js &
驱动脚本(保存为 `/tmp/cdp-debug.js`):
```javascript
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(`已暂停:${reason} @ ${top.url}:${top.location.lineNumber + 1}`);
// 遍历作用域获取局部变量
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);
}
}
}
// 在暂停的帧中执行表达式
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();
// 通过 URL 正则 + 行号设置断点
await Debugger.setBreakpointByUrl({
urlRegex: '.*app\\.tsx$',
lineNumber: 119, // 从0开始计数
columnNumber: 0,
});
await Runtime.runIfWaitingForDebugger();
})();运行脚本:
bash
node /tmp/cdp-debug.jsHermes 专属说明: 不在 中。如果不想污染项目依赖,可安装到临时目录:
chrome-remote-interfaceui-tui/package.jsonbash
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.jsDebugging Hermes ui-tui
调试 Hermes ui-tui
The TUI is built Ink + tsx. Two common scenarios:
TUI 基于 Ink + tsx 构建。以下是两种常见场景:
Debugging a single Ink component under dev
开发模式下调试单个 Ink 组件
ui-tui/package.jsonnpm run dev--inspect-brkbash
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.jsui-tui/package.jsonnpm run dev--inspect-brkbash
cd /home/bb/hermes-agent/ui-tui
npm run build # 先执行一次构建生成 dist/,避免首次加载时需要转译
node --inspect-brk dist/entry.jsIn 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.node inspect -p <node pid>
然后在 `debug>` 提示符中:
sb('dist/app.js', 220) # 或设置到可疑的渲染代码位置
cont
暂停后,输入 `repl` → 检查 `props`、状态引用、`useInput` 处理器值等。Debugging a running hermes --tui
hermes --tui调试运行中的 hermes --tui
hermes --tuiThe TUI spawns Node from the Python CLI. Easiest path:
bash
undefinedTUI 会从 Python CLI 启动 Node 进程。最简单的方法:
bash
undefined1. Launch TUI
1. 启动 TUI
hermes --tui &
TUI_PID=$(pgrep -f 'ui-tui/dist/entry' | head -1)
hermes --tui &
TUI_PID=$(pgrep -f 'ui-tui/dist/entry' | head -1)
2. Enable inspector on that Node PID
2. 为该 Node PID 启用调试器
kill -SIGUSR1 "$TUI_PID"
kill -SIGUSR1 "$TUI_PID"
3. Find the WS URL
3. 获取 WS URL
curl -s http://127.0.0.1:9229/json/list | jq -r '.[0].webSocketDebuggerUrl'
curl -s http://127.0.0.1:9229/json/list | jq -r '.[0].webSocketDebuggerUrl'
4. Attach
4. 附加调试器
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(...)`.node inspect ws://127.0.0.1:9229/<uuid>
与 TUI 交互(在其窗口中输入内容)会继续执行代码;你的调试器可以通过 `sb(...)` 设置的断点随时暂停它。Debugging _SlashWorker
/ PTY child processes
_SlashWorker调试 _SlashWorker
/ PTY 子进程
_SlashWorkerThose are Python, not Node — use the skill for them. Only Node portions (Ink UI, tui_gateway client, tsx-run tests under ) use this skill.
python-debugpyui-tui/这些是 Python 进程,而非 Node 进程——请使用 技能进行调试。只有 Node 相关部分(Ink UI、tui_gateway 客户端、 下的 tsx-run 测试)使用本技能。
python-debugpyui-tui/Running Vitest Tests Under the Debugger
在调试器下运行 Vitest 测试
bash
cd /home/bb/hermes-agent/ui-tuibash
cd /home/bb/hermes-agent/ui-tuiRun 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 <pid>`, 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.node --inspect-brk ./node_modules/vitest/vitest.mjs run --no-file-parallelism src/app/foo.test.tsx
在另一个终端中:`node inspect -p <pid>`,然后输入 `sb('src/app/foo.tsx', 42)`、`cont`。
使用 `--no-file-parallelism`(vitest)或 `--runInBand`(jest)确保仅存在一个工作进程——调试进程池会非常麻烦。Heap Snapshots & CPU Profiles (Non-interactive)
堆快照与 CPU 分析报告(非交互式)
From the CDP driver above, swap Debugger for / :
HeapProfilerProfilerjavascript
// 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 tabjavascript
// 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(''));在上述 CDP 驱动脚本中,将 Debugger 替换为 / :
HeapProfilerProfilerjavascript
undefinedCommon Pitfalls
CPU 分析(持续5秒)
-
Wrong line numbers in TS source. Breakpoints hit the emitted JS, not the. Either (a) break in the built
.ts, or (b) enable sourcemaps (dist/*.js) and usenode --enable-source-maps— but only with CDP clients that follow sourcemaps.sb('src/app.tsx', N)CLI does not.node inspect -
vs
--inspect.--inspect-brkstarts the inspector but doesn't pause; your script races past your first breakpoint if you attach too late. Use--inspectwhen you need to set breakpoints before any code runs.--inspect-brk -
Port collisions. Default is. If multiple Node processes are inspecting, pass
9229(random port) and read the actual URL from--inspect=0:/json/listbashcurl -s http://127.0.0.1:9229/json/list # lists all inspectable targets on the host -
Child processes.on a parent does NOT inspect its children. Use
--inspectto propagate to every child; be aware they all need unique ports (Node auto-increments whenNODE_OPTIONS='--inspect-brk' node parent.jsis inherited).NODE_OPTIONS='--inspect' -
Background kills. If youout of
Ctrl+Cwhile the target is paused, the target stays paused. Eithernode inspectfirst, orcontthe target explicitly.kill -
Runningthrough an agent terminal. It's a PTY-friendly REPL. In Hermes, launch it with
node inspectorterminal(pty=true)+background=true. Non-PTY foreground mode will work for one-shot commands but not for interactive stepping.process(action='submit', data='...') -
Security.exposes arbitrary code execution. Always bind to
--inspect=0.0.0.0:9229(the default) unless you have an isolated network.127.0.0.1
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));
Verification Checklist
在 Chrome DevTools 的 Performance 面板中打开 /tmp/cpu.cpuprofile
After setting up a debug session, verify:
- returns exactly the target you expect
curl -s http://127.0.0.1:9229/json/list - First breakpoint actually hits (if it doesn't, you likely missed or attached after execution completed)
--inspect-brk - Source listing at pause shows the right file (mismatch = sourcemap issue, see pitfall 1)
- in
exec process.pidreturns the PID you meant to attach torepl
```javascriptOne-Shot Recipes
堆快照
"Why is this variable undefined at line X?"
bash
node --inspect-brk script.js &
node inspect -p $!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(''));
undefineddebug>
常见陷阱
sb('script.js', X)
cont
-
TS 源代码行号不匹配。断点命中的是编译后的 JS,而非文件。解决方法:(a) 在编译后的
.ts中设置断点;或 (b) 启用 sourcemap(dist/*.js)并使用node --enable-source-maps——但仅适用于支持 sourcemap 的 CDP 客户端。sb('src/app.tsx', N)CLI 不支持此功能。node inspect -
与
--inspect的区别。--inspect-brk启动调试器但不暂停;如果附加过晚,脚本会跳过你的第一个断点。如果需要在代码运行前设置断点,请使用--inspect。--inspect-brk -
端口冲突。默认端口是。如果多个 Node 进程启用调试,传递
9229(随机端口)并从--inspect=0读取实际 URL:/json/listbashcurl -s http://127.0.0.1:9229/json/list # 列出主机上所有可调试的目标 -
子进程调试。父进程使用不会自动启用子进程的调试。使用
--inspect将调试配置传播给所有子进程;注意它们都需要唯一端口(当继承NODE_OPTIONS='--inspect-brk' node parent.js时,Node 会自动递增端口)。NODE_OPTIONS='--inspect' -
后台进程终止问题。如果在目标进程暂停时按退出
Ctrl+C,目标进程会保持暂停状态。请先执行node inspect恢复运行,或显式cont目标进程。kill -
通过代理终端运行。它是兼容 PTY 的交互式解释器。在 Hermes 中,使用
node inspect或terminal(pty=true)+background=true启动。非 PTY 前台模式仅适用于一次性命令,不适用于交互式单步调试。process(action='submit', data='...') -
安全问题。会暴露任意代码执行权限。除非处于隔离网络环境,否则始终绑定到默认的
--inspect=0.0.0.0:9229。127.0.0.1
paused. Now:
验证清单
repl
myVariable Object.keys(this)
**"What's the call path into this function?"**debug> sb('suspectFn')
debug> cont
设置调试会话后,请验证以下内容:
- 返回的目标与预期完全一致
curl -s http://127.0.0.1:9229/json/list - 第一个断点确实触发(如果未触发,可能是遗漏了 或在执行完成后才附加调试器)
--inspect-brk - 暂停时显示的源代码文件正确(不匹配则是 sourcemap 问题,参见陷阱1)
- 在 中执行
repl返回的 PID 与要附加的进程一致exec process.pid
paused on entry
一键式调试方案
debug> bt
**"This async chain hangs — where?"**“为什么第X行的变量是 undefined?”
bash
node --inspect-brk script.js &
node inspect -p $!Start with --inspect (no -brk), let it run to the hang, then:
debug>
debug> pause
debug> bt
sb('script.js', X)
cont
Now you see the stuck frame
已暂停。现在:
undefinedrepl
myVariable Object.keys(this)
**“调用这个函数的路径是什么?”**debug> sb('suspectFn')
debug> cont
—
在函数入口处暂停
—
debug> bt
**“这个异步链卡住了——在哪里?”**—
使用 --inspect 启动(不带 -brk),让它运行到卡住状态,然后:
—
debug> pause
debug> bt
—
现在可以看到卡住的帧
—
undefined