Development Node Inspect Debugger
На этой странице Отладка Node.js через --inspect + Chrome DevTools Protocol CLI.
Метаданные навыка¶
| Источник | Встроенный (устанавливается по умолчанию) |
| Путь | skills/software-development/node-inspect-debugger |
| Версия | 1.0.0 |
| Автор | Hermes Agent |
| Лицензия | MIT |
| Теги | debugging, nodejs, node-inspect, cdp, breakpoints, ui-tui |
| Связанные навыки | systematic-debugging, python-debugpy, debugging-hermes-tui-commands |
| ## Справочник: полный SKILL.md | |
| info | |
| Далее приведено полное определение навыка, которое Hermes загружает при его активации. Это то, что видит агент в качестве инструкций, когда навык активен. | |
| # Node.js Inspect Debugger | |
| ## Обзор | |
Когда console.log недостаточно, управляйте встроенным V8 inspector'ом Node программно из терминала. Вы получаете настоящие точки останова, шаг с заходом/без захода/с выходом, просмотр стека вызовов, дамп локальных и замыкающих переменных, а также вычисление произвольных выражений в приостановленном фрейме. |
|
| Два инструмента — выбирайте: | |
* node inspect — встроенный, без установки, REPL в CLI. Лучше всего для быстрых проверок. |
|
* ndb / CDP через chrome-remote-interface — скриптуемый из Node/Python; лучший выбор для автоматизации множества точек останова, сбора состояния между запусками или неинтерактивной отладки из цикла агента. |
Отдавайте предпочтение node inspect. Он всегда доступен, и REPL работает быстро.
Когда использовать¶
- Node-тест падает, и нужно увидеть промежуточное состояние
- ui-tui падает или ведёт себя неправильно, и нужно проверить состояние React/Ink до рендера
- Дочерние процессы tui_gateway (
_SlashWorker, PTY-мосты) работают некорректно - Нужно проверить значение в замыкании, до которого
console.logне добраться без модификации кода - Производительность: подключиться к запущенному процессу, чтобы снять CPU-профиль или снапшот кучи
Не используйте для: того, что console.log решает менее чем за минуту. Отладка с точками останова — более тяжёлый метод; применяйте его, когда отдача действительно оправдана.
Краткий справочник: REPL node inspect¶
Запуск с остановкой на первой строке:
[code]
node inspect path/to/script.js
# или с tsx
node --inspect-brk $(which tsx) path/to/script.ts
[/code]
Приглашение 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| backtrace (стек вызовов)
list(5)| показать 5 строк исходного кода вокруг текущей позиции
watch('expr')| вычислять выражение на каждой паузе
watchers| показать отслеживаемые выражения
repl| войти в REPL в текущей области видимости (Ctrl+C для выхода из REPL)
exec expr| вычислить выражение один раз
restart| перезапустить скрипт
kill| завершить скрипт
.exit| выйти из отладчика
В подрежиме repl: вводите любые JS-выражения, включая доступ к локальным переменным и переменным замыканий. Ctrl+C возвращает обратно в debug>.
Подключение к запущенному процессу¶
Когда процесс уже запущен (например, долгоживущий dev-сервер или шлюз TUI):
[code]
# 1. Отправить SIGUSR1, чтобы включить inspector в существующем процессе
kill -SIGUSR1
# Node выводит: Debugger listening on ws://127.0.0.1:9229/
# 2. Подключить отладчик CLI
node inspect -p <pid>
# или по URL
node inspect ws://127.0.0.1:9229/<uuid>
[/code]
Запуск процесса с inspector'ом с самого начала:
[code]
node --inspect script.js # слушать на 127.0.0.1:9229, продолжить выполнение
node --inspect-brk script.js # слушать И остановиться на первой строке
node --inspect=0.0.0.0:9230 script.js # свой хост:порт
[/code]
Для TypeScript через tsx:
[code]
node --inspect-brk --import tsx script.ts
# или старая версия tsx
node --inspect-brk -r tsx/cjs script.ts
[/code]
Программный CDP (скриптинг из терминала)¶
Когда нужно автоматизировать — установить много точек останова, захватить состояние области видимости, запрограммировать воспроизведение — используйте chrome-remote-interface:
[code]
npm i -g chrome-remote-interface # или локально в проекте
# Запустите ваш целевой скрипт:
node --inspect-brk=9229 target.js &
[/code]
Скрипт-драйвер (сохранить как /tmp/cdp-debug.js):
[code]
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}`);
// Обход областей видимости для локальных переменных
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();
})();
[/code] Запустите: [code] node /tmp/cdp-debug.js
[/code]
Примечание для Hermes: chrome-remote-interface НЕ входит в ui-tui/package.json. Установите его во временную директорию, если не хотите засорять проект:
[code]
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
[/code]
Отладка Hermes ui-tui¶
TUI построен на Ink + tsx. Два распространённых сценария:
Отладка отдельного Ink-компонента в процессе разработки¶
В ui-tui/package.json есть npm run dev (tsx --watch). Добавьте --inspect-brk, запустив tsx напрямую:
[code]
cd /home/bb/hermes-agent/ui-tui
npm run build # собрать dist/ один раз, чтобы не транспилировать при первой загрузке
node --inspect-brk dist/entry.js
# В другом терминале:
node inspect -p
[/code]
Затем внутри debug>:
[code]
sb('dist/app.js', 220) # или там, где подозрительный рендер
cont
[/code]
Когда выполнение остановится, repl → инспектируйте props, ссылки на state, значения обработчиков useInput и т.д.
Отладка запущенного hermes --tui¶
TUI запускает Node из Python CLI. Самый простой путь:
[code]
# 1. Запустить TUI
hermes --tui &
TUI_PID=$(pgrep -f 'ui-tui/dist/entry' | head -1)
# 2. Включить inspector на этом Node PID
kill -SIGUSR1 "$TUI_PID"
# 3. Найти WS URL
curl -s http://127.0.0.1:9229/json/list | jq -r '.[0].webSocketDebuggerUrl'
# 4. Подключиться
node inspect ws://127.0.0.1:9229/<uuid>
[/code]
Взаимодействие с TUI (ввод в его окне) продолжает выполнение; ваш отладчик может остановить его на точке останова в любом sb(...).
Отладка дочерних процессов _SlashWorker / PTY¶
Это Python, а не Node — используйте навык python-debugpy. Только части на Node (Ink UI, клиент tui_gateway, тесты tsx в ui-tui/) используют этот навык.
Запуск тестов Vitest под отладчиком¶
[code]
cd /home/bb/hermes-agent/ui-tui
# Запустить один тестовый файл с остановкой на входе
node --inspect-brk ./node_modules/vitest/vitest.mjs run --no-file-parallelism src/app/foo.test.tsx
[/code]
В другом терминале: node inspect -p <pid>, затем sb('src/app/foo.tsx', 42), cont.
Используйте --no-file-parallelism (vitest) или --runInBand (jest), чтобы работал только один воркер — отладка пула процессов мучительна.
Снапшоты кучи и CPU-профили (неинтерактивно)¶
Из CDP-драйвера выше замените Debugger на HeapProfiler / Profiler:
[code]
// CPU профиль на 5 секунд
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));
// Открыть /tmp/cpu.cpuprofile в Chrome DevTools → вкладка Performance
[/code]
[code]
// Снапшот кучи
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(''));
[/code]
Частые проблемы¶
- Неверные номера строк в TS-исходниках. Точки останова срабатывают на скомпилированном JS, а не на
.ts. Либо (а) ставьте точки останова в собранныхdist/*.js, либо (б) включите sourcemaps (node --enable-source-maps) и используйтеsb('src/app.tsx', N)— но только с CDP-клиентами, поддерживающими sourcemaps. CLInode inspectих не поддерживает. --inspectvs--inspect-brk.--inspectзапускает inspector, но не приостанавливает выполнение; ваш скрипт может пройти мимо первой точки останова, если вы подключитесь слишком поздно. Используйте--inspect-brk, когда нужно установить точки останова до выполнения любого кода.- Конфликты портов. По умолчанию используется
9229. Если несколько Node-процессов работают с inspector'ом, передайте--inspect=0(случайный порт) и прочитайте фактический URL из/json/list: [code] curl -s http://127.0.0.1:9229/json/list # список всех инспектируемых целей на хосте
[/code]
4. Дочерние процессы. --inspect на родительском процессе НЕ инспектирует его дочерние процессы. Используйте NODE_OPTIONS='--inspect-brk' node parent.js, чтобы передать параметр каждому дочернему процессу; учтите, что всем нужны уникальные порты (Node автоматически увеличивает порт, когда наследуется NODE_OPTIONS='--inspect').
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 возвращает именно ту цель, которую вы ожидаете
* Первая точка останова действительно срабатывает (если нет — вы, вероятно, пропустили --inspect-brk или подключились после завершения выполнения)
* Показ исходного кода на паузе отображает правильный файл (несовпадение = проблема с sourcemaps, см. проблему 1)
* exec process.pid в repl возвращает PID, к которому вы намеревались подключиться
Разовые рецепты¶
«Почему эта переменная undefined на строке X?»
[code]
node --inspect-brk script.js &
node inspect -p $!
# debug>
sb('script.js', X)
cont
# paused. Теперь:
repl
> myVariable
> Object.keys(this)
[/code]
«Какой путь вызова ведёт в эту функцию?»
[code]
debug> sb('suspectFn')
debug> cont
# остановились на входе
debug> bt
[/code]
«Эта асинхронная цепочка зависает — где?»
[code]
# Запустите с --inspect (без -brk), дайте выполниться до зависания, затем:
debug> pause
debug> bt
# Теперь вы видите застрявший фрейм
[/code]
* Метаданные навыка
* Справочник: полный SKILL.md
* Обзор
* Когда использовать
* Краткий справочник: REPL node inspect
* Подключение к запущенному процессу
* Программный CDP (скриптинг из терминала)
* Отладка Hermes ui-tui
* Отладка отдельного Ink-компонента в процессе разработки
* Отладка запущенного hermes --tui
* Отладка дочерних процессов _SlashWorker / PTY
* Запуск тестов Vitest под отладчиком
* Снапшоты кучи и CPU-профили (неинтерактивно)
* Частые проблемы
* Контрольный список проверки
* Разовые рецепты