跳到主要內容

Node Inspect Debugger

通過 --inspect + Chrome DevTools Protocol CLI 調試 Node.js。

技能元數據

來源捆綁(默認安裝)
路徑skills/software-development/node-inspect-debugger
版本1.0.0
作者Hermes Agent
許可證MIT
平臺linux, macos, windows
標籤debugging, nodejs, node-inspect, cdp, breakpoints, ui-tui
相關技能systematic-debugging, python-debugpy

參考:完整 SKILL.md

信息

以下是 Hermes 在觸發此技能時加載的完整技能定義。這是技能激活時代理看到的指令。

Node.js Inspect Debugger

概述

console.log 不夠用時,可以從終端以編程方式驅動 Node 內置的 V8 檢查器。你可以獲得真正的斷點、單步進入/跳過/跳出、調用棧遍歷、局部/閉包作用域轉儲,以及在暫停幀中執行任意表達式求值。

兩種工具,任選其一:

  • node inspect — 內置,零安裝,CLI REPL。最適合快速探查。
  • ndb / 通過 chrome-remote-interface 使用 CDP — 可從 Node/Python 進行腳本化;最適合需要自動化設置多個斷點、跨運行收集狀態,或在代理循環中進行非交互式調試的場景。

優先使用 node inspect 它始終可用且 REPL 速度快。

何時使用

  • Node 測試失敗,你需要查看中間狀態
  • ui-tui 崩潰或行為異常,你想檢查渲染前的 React/Ink 狀態
  • tui_gateway 子進程(_SlashWorker、PTY 橋接工作進程)行為異常
  • 你需要檢查閉包中的某個值,而如果不打補丁,console.log 無法訪問該值
  • 性能分析:附加到正在運行的進程以捕獲 CPU 性能剖析或堆快照

不要用於: console.log 能在一分鐘內解決的問題。基於斷點的調試開銷較大;僅在收益顯著時使用。

快速參考:node inspect REPL

在第一行暫停啟動:

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

debug> 提示符接受以下命令:

命令操作
ccont繼續執行
nnext單步跳過
sstep單步進入
oout單步跳出
pause暫停正在運行的代碼
sb('file.js', 42)在 file.js 的第 42 行設置斷點
sb(42)在當前文件的第 42 行設置斷點
sb('functionName')在調用函數時中斷
cb('file.js', 42)清除斷點
breakpoints列出所有斷點
bt回溯(調用棧)
list(5)顯示當前位置周圍的 5 行源代碼
watch('expr')每次暫停時計算 expr
watchers顯示被監視的表達式
repl進入當前作用域的 REPL(按 Ctrl+C 退出 REPL)
exec expr計算一次表達式
restart重啟腳本
kill終止腳本
.exit退出調試器

repl 子模式下: 輸入任何 JS 表達式,包括訪問局部變量/閉包變量。按 Ctrl+C 返回 debug>

附加到正在運行的進程

當進程已經在運行時(例如長期運行的開發服務器或 TUI 網關):

# 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>

從頭開始啟動帶有檢查器的進程:

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

對於通過 tsx 運行的 TypeScript:

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

編程式 CDP(從終端進行腳本化)

當你想要自動化操作時——設置多個斷點、捕獲作用域狀態、編寫可復現的腳本——使用 chrome-remote-interface

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

驅動腳本(保存為 /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

運行它:

```bash
node /tmp/cdp-debug.js

Hermes 特定說明:ui-tui/package.json 中不包含 chrome-remote-interface。如果你不想弄亂項目,可以將其安裝到臨時位置:

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

調試 Hermes ui-tui

TUI 基於 Ink + tsx 構建。兩種常見場景:

在開發環境下調試單個 Ink 組件

ui-tui/package.json 中有 npm run dev(tsx --watch)。通過直接運行 tsx 添加 --inspect-brk

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: \{#debugging-hermes-ui-tui}
node inspect -p <node pid>

然後在 debug> 內部:

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

當它暫停時,進入 repl → 檢查 props、狀態引用、useInput 處理程序值等。

調試正在運行的 hermes --tui

TUI 由 Python CLI 生成 Node 進程。最簡單的路徑:

# 1. Launch TUI \{#debugging-a-single-ink-component-under-dev}
hermes --tui &
TUI_PID=$(pgrep -f 'ui-tui/dist/entry' | head -1)

# 2. Enable inspector on that Node PID \{#debugging-a-running-hermes---tui}
kill -SIGUSR1 "$TUI_PID"

# 3. Find the WS URL \{#debugging-_slashworker--pty-child-processes}
curl -s http://127.0.0.1:9229/json/list | jq -r '.[0].webSocketDebuggerUrl'

# 4. Attach \{#running-vitest-tests-under-the-debugger}
node inspect ws://127.0.0.1:9229/<uuid>

與 TUI 交互(在其窗口中輸入內容)會繼續推進執行;你的調試器可以在任何 sb(...) 處通過斷點暫停它。

調試 _SlashWorker / PTY 子進程

這些是 Python 進程,不是 Node —— 請使用 python-debugpy 技能來調試它們。只有 Node 部分(Ink UI、tui_gateway 客戶端、ui-tui/ 下的 tsx-run 測試)使用此技能。

在調試器下運行 Vitest 測試

cd /home/bb/hermes-agent/ui-tui
# Run a single test file paused on entry \{#heap-snapshots--cpu-profiles-non-interactive}
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),以確保僅存在一個工作進程——調試進程池非常痛苦。

堆快照與 CPU 性能分析(非交互式)

在上述 CDP 驅動中,將 Debugger 替換為 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(''));

常見陷阱

  1. TS 源碼中的行號錯誤。 斷點命中的是生成的 JS,而非 .ts 文件。要麼 (a) 在構建後的 dist/*.js 中打斷點,要麼 (b) 啟用 sourcemaps(node --enable-source-maps)並使用 sb('src/app.tsx', N)——但僅限支持跟隨 sourcemaps 的 CDP 客戶端。node inspect CLI 不支持。

  2. --inspect--inspect-brk --inspect 啟動檢查器但不會暫停;如果附加過晚,腳本會在你到達第一個斷點之前快速執行完畢。當需要在任何代碼運行之前設置斷點時,請使用 --inspect-brk

  3. 端口衝突。 默認端口為 9229。如果有多個 Node 進程正在被檢查,請傳遞 --inspect=0(隨機端口)並從 /json/list 讀取實際 URL:

    curl -s http://127.0.0.1:9229/json/list   # lists all inspectable targets on the host
  4. 子進程。 父進程上的 --inspect 不會檢查其子進程。使用 NODE_OPTIONS='--inspect-brk' node parent.js 以傳播到每個子進程;請注意,它們都需要唯一的端口(當繼承 NODE_OPTIONS='--inspect' 時,Node 會自動遞增端口)。

  5. 後臺終止。 如果在目標暫停時通過 Ctrl+C 退出 node inspect,目標將保持暫停狀態。請先執行 cont,或顯式 kill 目標進程。

  6. 通過代理終端運行 node inspect 它是一個對 PTY 友好的 REPL。在 Hermes 中,使用 terminal(pty=true)background=true + process(action='submit', data='...') 啟動它。非 PTY 前臺模式適用於一次性命令,但不適用於交互式單步調試。

  7. 安全性。 --inspect=0.0.0.0:9229 會暴露任意代碼執行風險。除非處於隔離網絡中,否則始終綁定到 127.0.0.1(默認值)。

驗證清單

設置調試會話後,請驗證:

  • curl -s http://127.0.0.1:9229/json/list 返回 exactly 你期望的目標
  • 第一個斷點確實命中(如果沒有命中,你可能遺漏了 --inspect-brk 或在執行完成後才附加)
  • 暫停時的源碼列表顯示正確的文件(不匹配 = sourcemap 問題,參見陷阱 1)
  • repl 中執行 exec process.pid 返回你打算附加的 PID

一次性方案

“為什麼第 X 行的這個變量是 undefined?”

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

“進入此函數的調用路徑是什麼?”

debug> sb('suspectFn')
debug> cont
# paused on entry \{#one-shot-recipes}
debug> bt

“這個異步鏈掛起了——在哪裡?”

# Start with --inspect (no -brk), let it run to the hang, then:
debug> pause
debug> bt
# Now you see the stuck frame
```,
lineNumber: 119, // 0-indexed
columnNumber: 0,
});

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

運行它:

node /tmp/cdp-debug.js

Hermes 特定說明:ui-tui/package.json 中不包含 chrome-remote-interface。如果你不想弄亂項目,可以將其安裝到臨時位置:

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

調試 Hermes ui-tui

TUI 基於 Ink + tsx 構建。兩種常見場景:

在開發環境下調試單個 Ink 組件

ui-tui/package.json 中有 npm run dev(tsx --watch)。通過直接運行 tsx 添加 --inspect-brk

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>

然後在 debug> 內部:

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

當它暫停時,進入 repl → 檢查 props、狀態引用、useInput 處理程序值等。

調試正在運行的 hermes --tui

TUI 由 Python CLI 生成 Node 進程。最簡單的路徑:

# 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>

與 TUI 交互(在其窗口中輸入內容)會繼續推進執行;你的調試器可以在任何 sb(...) 處通過斷點暫停它。

調試 _SlashWorker / PTY 子進程

這些是 Python 進程,不是 Node —— 請使用 python-debugpy 技能來調試它們。只有 Node 部分(Ink UI、tui_gateway 客戶端、ui-tui/ 下的 tsx-run 測試)使用此技能。

在調試器下運行 Vitest 測試

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

在另一個終端中:node inspect -p <pid>,然後執行 sb('src/app/foo.tsx', 42)cont

使用 --no-file-parallelism(vitest)或 --runInBand(jest),以確保僅存在一個工作進程——調試進程池非常痛苦。

堆快照與 CPU 性能分析(非交互式)

在上述 CDP 驅動中,將 Debugger 替換為 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(''));

常見陷阱

  1. TS 源碼中的行號錯誤。 斷點命中的是生成的 JS,而非 .ts 文件。要麼 (a) 在構建後的 dist/*.js 中打斷點,要麼 (b) 啟用 sourcemaps(node --enable-source-maps)並使用 sb('src/app.tsx', N)——但僅限支持跟隨 sourcemaps 的 CDP 客戶端。node inspect CLI 不支持。

  2. --inspect--inspect-brk --inspect 啟動檢查器但不會暫停;如果附加過晚,腳本會在你到達第一個斷點之前快速執行完畢。當需要在任何代碼運行之前設置斷點時,請使用 --inspect-brk

  3. 端口衝突。 默認端口為 9229。如果有多個 Node 進程正在被檢查,請傳遞 --inspect=0(隨機端口)並從 /json/list 讀取實際 URL:

    curl -s http://127.0.0.1:9229/json/list   # lists all inspectable targets on the host
  4. 子進程。 父進程上的 --inspect 不會檢查其子進程。使用 NODE_OPTIONS='--inspect-brk' node parent.js 以傳播到每個子進程;請注意,它們都需要唯一的端口(當繼承 NODE_OPTIONS='--inspect' 時,Node 會自動遞增端口)。

  5. 後臺終止。 如果在目標暫停時通過 Ctrl+C 退出 node inspect,目標將保持暫停狀態。請先執行 cont,或顯式 kill 目標進程。

  6. 通過代理終端運行 node inspect 它是一個對 PTY 友好的 REPL。在 Hermes 中,使用 terminal(pty=true)background=true + process(action='submit', data='...') 啟動它。非 PTY 前臺模式適用於一次性命令,但不適用於交互式單步調試。

  7. 安全性。 --inspect=0.0.0.0:9229 會暴露任意代碼執行風險。除非處於隔離網絡中,否則始終綁定到 127.0.0.1(默認值)。

驗證清單

設置調試會話後,請驗證:

  • curl -s http://127.0.0.1:9229/json/list 返回 exactly 你期望的目標
  • 第一個斷點確實命中(如果沒有命中,你可能遺漏了 --inspect-brk 或在執行完成後才附加)
  • 暫停時的源碼列表顯示正確的文件(不匹配 = sourcemap 問題,參見陷阱 1)
  • repl 中執行 exec process.pid 返回你打算附加的 PID

一次性方案

“為什麼第 X 行的這個變量是 undefined?”

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

“進入此函數的調用路徑是什麼?”

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

“這個異步鏈掛起了——在哪裡?”

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