mcpField
N
node-inspect-debugger

node-inspect-debugger

github.com/nousresearch開発258.1k397 回インストール今日更新
リポジトリ

node-inspect-debugger について

Node.js 開発者向けのデバッグスキルです。組み込みの V8 インスペクターやスクリプト可能な CDP クライアントを使い、ブレークポイント、スコープ確認、実行中プロセスへの接続、CPU・ヒープのプロファイリングを扱います。

主な機能

  1. 01組み込み node inspect CLI でデバッグ
  2. 02ブレークポイント設定とスコープ確認
  3. 03実行中の Node.js プロセスに接続
  4. 04CDP によるデバッグ自動化
  5. 05CPU プロファイルとヒープスナップショットの取得

活用シーン

  1. 01失敗した Node テストの中間状態を確認
  2. 02描画前の UI 状態を調査
  3. 03稼働中の開発サーバーをデバッグ
  4. 04CPU やヒープの診断情報を収集

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/nousresearch/hermes-agent --skill node-inspect-debugger

インストール後、Claude Code で/node-inspect-debuggerを入力して読み込まれたか確認します。

詳細

Node.js Inspect Debugger

概要

console.log だけでは足りない場合は、ターミナルから Node 組み込みの V8 インスペクターをプログラムで操作できます。実際のブレークポイント、ステップイン/ステップオーバー/ステップアウト、コールスタックの追跡、ローカルスコープとクロージャースコープのダンプ、一時停止中のフレームでの任意の式の評価ができます。

2 つのツールから選べます。

  • node inspect — 組み込みで、インストール不要の CLI REPL です。手早く調べるのに最適です。
  • ndb / CDP via chrome-remote-interface — Node/Python からスクリプトで操作できます。多数のブレークポイントを自動化したい場合、実行をまたいで状態を収集したい場合、またはエージェントのループから非対話形式でデバッグしたい場合に最適です。

まずは node inspect を使ってください。 常に利用でき、REPL も高速です。

使用する場面

  • Node のテストが失敗し、中間状態を確認したい場合
  • ui-tui がクラッシュしたり、誤動作したりして、レンダリング前の React/Ink の状態を調べたい場合
  • tui_gateway の子プロセス(_SlashWorker、PTY ブリッジワーカー)が正常に動作しない場合
  • パッチを当てずに console.log からはアクセスできないクロージャー内の値を確認したい場合
  • パフォーマンス調査:実行中のプロセスにアタッチして、CPU プロファイルまたはヒープスナップショットを取得したい場合

使用しない場面: 1 分以内に console.log で解決できること。ブレークポイントを使ったデバッグは手間がかかるため、十分な効果が見込める場合に使ってください。

クイックリファレンス:node inspect REPL

最初の行で一時停止して起動します。

node inspect path/to/script.js
# or with tsx
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')一時停止のたびに expr を評価
watchers監視中の式を表示
repl現在のスコープで REPL に入る(Ctrl+C で REPL を終了)
exec expr式を 1 回評価
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 &

ドライバースクリプト(~/.hermes/cache/scratch/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();
})();

実行します。

node ~/.hermes/cache/scratch/cdp-debug.js

Hermes 固有の注意:chrome-remote-interface は ui-tui/package.json に含まれていません。プロジェクトを汚したくない場合は、一時的な場所にインストールしてください。

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

Hermes ui-tui のデバッグ

TUI は Ink + tsx で構築されています。よくあるシナリオは 2 つあります。

開発環境で単一の Ink コンポーネントをデバッグする

ui-tui/package.json には npm run dev(tsx --watch)があります。tsx を直接実行して --inspect-brk を追加します。

cd <hermes-agent-repo>/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、state の ref、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 子プロセスをデバッグする

これらは Node ではなく Python です。python-debugpy スキルを使ってください。このスキルを使うのは、Node 部分(Ink UI、tui_gateway クライアント、ui-tui/ 内の tsx-run テスト)のみです。

デバッガーで Vitest のテストを実行する

cd <hermes-agent-repo>/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 を実行します。

ワーカーを 1 つだけにするため、--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('~/.hermes/cache/scratch/cpu.cpuprofile', JSON.stringify(profile));
// Open ~/.hermes/cache/scratch/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('~/.hermes/cache/scratch/heap.heapsnapshot', chunks.join(''));

よくある落とし穴

  1. TS ソースの行番号が正しくない。 ブレークポイントは .ts ではなく、コンパイル後の JS でヒットします。(a)ビルドされた dist/*.js にブレークポイントを設定するか、(b)ソースマップを有効にして(node --enable-source-maps)、sb('src/app.tsx', N) を使用します。ただし、ソースマップに対応した 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. バックグラウンドプロセスの停止。 対象が一時停止しているときに node inspect を Ctrl+C で終了しても、対象は一時停止したままです。先に 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 が、想定した対象だけを返す
  • 最初のブレークポイントで実際に停止する(停止しない場合は、--inspect-brk を指定し忘れたか、実行完了後に接続した可能性があります)
  • 一時停止中に表示されるソースの一覧に正しいファイルが表示される(不一致の場合はソースマップに問題があります。「よくある落とし穴」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

コメント