Перейти к основному содержимому

Python Debugpy

Отладка Python: pdb REPL + debugpy remote (DAP).

Метаданные навыка​

ИсточникВстроенный (устанавливается по умолчанию)
Путьskills/software-development/python-debugpy
Версия1.0.0
АвторVibeOS
ЛицензияMIT
Платформыlinux, macos
Тегиdebugging, python, pdb, debugpy, breakpoints, dap, post-mortem
Связанные навыкиsystematic-debugging, node-inspect-debugger, debugging-vibeos-tui-commands

Справочник: полный SKILL.md​

к сведению

Ниже приведено полное описание навыка, которое VibeOS загружает при его активации. Это те инструкции, которые видит агент, когда навык активен.

Отладчик Python (pdb + debugpy)

Обзор​

Три инструмента, выбираемых по ситуации:

ИнструментКогда использовать
breakpoint() + pdbЛокально, интерактивно, проще всего. Добавьте breakpoint() в исходный код, запустите обычным образом — получите REPL на этой строке.
python -m pdbЗапустить существующий скрипт под pdb без изменения исходного кода. Полезно для быстрого исследования.
debugpyУдалённо / без головы / «подключиться к уже запущенному процессу». Использует протокол DAP, управляется из терминала, подходит для долгоживущих процессов (gateway, daemon, дочерние процессы PTY).

Начинайте с breakpoint(). Это самое простое, что работает.

Когда использовать​

  • Тест падает, и traceback не показывает, почему значение неверно
  • Нужно пройтись по функции шагами и посмотреть, как изменяется коллекция
  • Долгоживущий процесс (vibeos gateway, tui_gateway) работает некорректно, и его нельзя перезапустить
  • Посмертный анализ: в коде, близком к продакшену, возникло исключение, и нужно изучить локальные переменные в месте сбоя
  • Дочерний процесс / подпроцесс (Python _SlashWorker, PTY bridge worker) является истинным местом ошибки

Не используйте для: того, что print() / logging.debug решают меньше чем за минуту, или того, что pytest -vv --tb=long --showlocals уже показывает.

Краткий справочник по pdb​

В любом приглашении pdb ((Pdb)):

КомандаДействие
h / h cmdсправка
nследующая строка (шаг с обходом)
sшаг с заходом
rвыход из текущей функции
cпродолжение
unt Nпродолжать до строки N
j Nперейти к строке N (только в той же функции)
l / llпоказать исходный код вокруг текущей строки / всей функции
wwhere (стек вызовов)
u / dпереместиться вверх / вниз по стеку
aвывести аргументы текущей функции
p expr / pp exprвывести / красиво вывести выражение
display exprавтоматически выводить выражение на каждой остановке
b file:lineустановить точку останова
b funcостановиться на входе в функцию
b file:line, condусловная точка останова
cl Nудалить точку останова N
tbreak file:lineодноразовая точка останова
!stmtвыполнить произвольный код Python (включая присваивания)
interactвойти в полноценный REPL Python в текущей области видимости (Ctrl+D для выхода)
qвыход

Команда interact — самая мощная: можно импортировать что угодно, исследовать сложные объекты, даже вызывать методы, изменяющие состояние. Локальные переменные по умолчанию доступны только для чтения; используйте !x = 42 из приглашения (Pdb) для изменения.

Рецепт 1: Локальная точка останова​

Самый простой способ. Отредактируйте файл:

def compute(x, y):
result = some_helper(x)
breakpoint() # <-- здесь происходит вход в pdb
return result + y

Запустите код обычным образом. Вы окажетесь на строке с breakpoint() с полным доступом к локальным переменным.

Не забудьте удалить breakpoint() перед коммитом. Используйте git diff или pre-commit grep:

rg -n 'breakpoint\(\)' --type py

Рецепт 2: Запуск скрипта под pdb (без изменения исходного кода)​

python -m pdb path/to/script.py arg1 arg2
# Остановка на первой строке скрипта
(Pdb) b path/to/script.py:42
(Pdb) c

Рецепт 3: Отладка pytest-теста​

Тестовый раннер vibeos и pytest поддерживают это:

# Вход в pdb при падении (или при любом возникшем исключении):
scripts/run_tests.sh tests/path/to/test_file.py::test_name --pdb

# Вход в pdb в НАЧАЛЕ теста:
scripts/run_tests.sh tests/path/to/test_file.py::test_name --trace

# Показать локальные переменные в traceback без pdb:
scripts/run_tests.sh tests/path/to/test_file.py --showlocals --tb=long

Примечание: scripts/run_tests.sh по умолчанию использует xdist (-n 4), а pdb НЕ работает под xdist. Добавьте -p no:xdist или запустите один тест с -n 0:

scripts/run_tests.sh tests/foo_test.py::test_bar --pdb -p no:xdist
# или
source .venv/bin/activate
python -m pytest tests/foo_test.py::test_bar --pdb

Это обходит гарантии изолированного окружения — нормально для отладки, но перед отправкой перезапустите под обёрткой для подтверждения.

Рецепт 4: Посмертный анализ любого исключения​

import pdb, sys
try:
run_the_thing()
except Exception:
pdb.post_mortem(sys.exc_info()[2])

Или оберните весь скрипт:

python -m pdb -c continue script.py
# Когда произойдёт сбой, pdb перехватит его, и вы окажетесь в фрейме исключения

Или установите глобальный хук в repl/jupyter:

import sys
def excepthook(etype, value, tb):
import pdb; pdb.post_mortem(tb)
sys.excepthook = excepthook

Рецепт 5: Удалённая отладка с debugpy (подключение к работающему процессу)​

Для долгоживущих процессов: VibeOS gateway, tui_gateway, демон, процесс, который уже работает некорректно и не может быть чисто перезапущен.

Настройка​

source /home/bb/vibeos-agent/.venv/bin/activate
pip install debugpy

Схема A: Изменение исходного кода — процесс ждёт отладчик при запуске​

Добавьте в начало точки входа (или внутрь функции, которую хотите отлаживать):

import debugpy
debugpy.listen(("127.0.0.1", 5678))
print("debugpy слушает порт 5678, ожидание клиента...", flush=True)
debugpy.wait_for_client()
debugpy.breakpoint() # опционально: приостановиться сразу после подключения

Запустите процесс; он заблокируется на wait_for_client().

Схема B: Без изменения исходного кода — запуск с -m debugpy​

python -m debugpy --listen 127.0.0.1:5678 --wait-for-client your_script.py arg1

Эквивалент для модульной точки входа:

python -m debugpy --listen 127.0.0.1:5678 --wait-for-client -m your.module

Схема C: Подключение к уже запущенному процессу​

Требуется PID и предустановленный debugpy в окружении целевого процесса:

python -m debugpy --listen 127.0.0.1:5678 --pid <pid>
# debugpy внедряется в процесс. Затем подключите клиент, как описано ниже.

Некоторые настройки ядра/безопасности блокируют внедрение на основе ptrace (/proc/sys/kernel/yama/ptrace_scope). Исправление:

echo 0 | sudo tee /proc/sys/kernel/yama/ptrace_scope

Подключение клиента из терминала​

Самый простой терминальный DAP-клиент — это CLI VS Code или небольшой скрипт. Внутри VibeOS у вас есть два практических варианта:

Вариант 1: Собственный CLI REPL debugpy — не официальная функция, а небольшой скрипт DAP-клиента:

# /tmp/dap_client.py
import socket, json, itertools, time, sys

HOST, PORT = "127.0.0.1", 5678
s = socket.create_connection((HOST, PORT))
seq = itertools.count(1)

def send(msg):
msg["seq"] = next(seq)
body = json.dumps(msg).encode()
s.sendall(f"Content-Length: {len(body)}\r\n\r\n".encode() + body)

def recv():
header = b""
while b"\r\n\r\n" not in header:
header += s.recv(1)
length = int(header.decode().split("Content-Length:")[1].split("\r\n")[0].strip())
body = b""
while len(body) < length:
body += s.recv(length - len(body))
return json.loads(body)

send({"type": "request", "command": "initialize", "arguments": {"adapterID": "python"}})
print(recv())
send({"type": "request", "command": "attach", "arguments": {}})
print(recv())
send({"type": "request", "command": "setBreakpoints",
"arguments": {"source": {"path": sys.argv[1]},
"breakpoints": [{"line": int(sys.argv[2])}]}})
print(recv())
send({"type": "request", "command": "configurationDone"})
# ... цикл чтения событий и отправки continue/stepIn/...

Это нормально для разовой автоматизации, но неудобно как интерактивный интерфейс.

Вариант 2: Подключение из VS Code / Cursor / Zed — если у пользователя открыт один из них, он может добавить launch.json:

{
"name": "Подключиться к VibeOS",
"type": "debugpy",
"request": "attach",
"connect": { "host": "127.0.0.1", "port": 5678 },
"justMyCode": false,
"pathMappings": [
{ "localRoot": "${workspaceFolder}", "remoteRoot": "/home/bb/vibeos-agent" }
]
}

Вариант 3: Отказаться от DAP, использовать remote-pdb — обычно это то, что вам на самом деле нужно от терминального агента:

pip install remote-pdb

В вашем коде:

from remote_pdb import set_trace
set_trace(host="127.0.0.1", port=4444) # блокируется до подключения

Затем из терминала:

nc 127.0.0.1 4444
# Вы получаете приглашение (Pdb) точно так же, как при локальной отладке.

remote-pdb — самый чистый выбор, удобный для агента, когда протокол DAP от debugpy избыточен. Используйте debugpy только когда вам действительно нужна интеграция с IDE.

Отладка специфичных процессов VibeOS​

Тесты​

См. Рецепт 3. Всегда добавляйте -p no:xdist или запускайте отдельные тесты без xdist.

run_agent.py / CLI — одноразовый запуск​

Проще всего: добавьте breakpoint() рядом с подозрительной строкой, затем запустите vibeos обычным образом. Управление вернётся в ваш терминал в точке остановки.

Подпроцесс tui_gateway (запускается vibeos --tui)​

Gateway работает как дочерний процесс Node TUI. Варианты:

A. Изменить исходный код gateway:

# tui_gateway/server.py в начале serve()
import debugpy
debugpy.listen(("127.0.0.1", 5678))
debugpy.wait_for_client()

Запустите vibeos --tui. TUI будет выглядеть замороженным (его бэкенд ожидает). Подключите клиент; выполнение возобновится, когда вы нажмёте continue.

B. Использовать remote-pdb в конкретном обработчике:

from remote_pdb import set_trace
set_trace(host="127.0.0.1", port=4444) # в RPC-обработчике, который нужно перехватить

Вызовите соответствующую slash-команду из TUI, затем nc 127.0.0.1 4444 в другом терминале.

Подпроцесс _SlashWorker​

Та же схема — remote-pdb с set_trace() внутри пути exec воркера. Воркер сохраняется между slash-командами, поэтому первый вызов блокируется до подключения; последующие slash-команды проходят нормально, если вы не переустановите точку.

Gateway (gateway/run.py)​

Долгоживущий процесс. Используйте remote-pdb в обработчике или debugpy с --wait-for-client, если вы всё равно перезапускаете gateway.

Частые ошибки​

  1. pdb под pytest-xdist молча ничего не делает. Вы не увидите приглашения, тест просто зависнет. Всегда используйте -p no:xdist или -n 0.

  2. breakpoint() в CI / не в TTY-контексте вешает процесс. Безопасно только локально; никогда не коммитьте. Добавьте pre-commit grep как страховку.

  3. PYTHONBREAKPOINT=0 отключает все вызовы breakpoint(). Проверьте переменную окружения, если ваша точка останова не срабатывает:

    echo $PYTHONBREAKPOINT
  4. debugpy.listen блокируется только если вы также вызываете wait_for_client(). Без неё выполнение продолжается, и ваша первая точка останова может сработать до подключения клиента.

  5. Подключение к PID не работает на усиленных ядрах. ptrace_scope=1 (по умолчанию в Ubuntu) разрешает ptrace только дочерних процессов того же пользователя. Обход: echo 0 > /proc/sys/kernel/yama/ptrace_scope (требует root) или запуск под debugpy с самого начала.

  6. Потоки. pdb отлаживает только текущий поток. Для многопоточного кода используйте debugpy (DAP с поддержкой потоков) или установите threading.settrace() для каждого потока.

  7. asyncio. pdb работает в корутинах, но await внутри pdb требует Python 3.13+ или await из режима interact на старых версиях. Для 3.11/3.12 используйте трюки с asyncio.run_coroutine_threadsafe или !stmt-основанные await через asyncio.ensure_future.

  8. scripts/run_tests.sh удаляет учётные данные и устанавливает HOME=&lt;tmpdir&gt;. Если ваша ошибка зависит от конфигурации пользователя или реальных API-ключей, она не воспроизведётся под обёрткой. Сначала отлаживайте с помощью чистого pytest для воспроизведения, затем перепроверьте под обёрткой.

  9. Forking / multiprocessing. pdb не следит за форками. Каждый дочерний процесс требует собственного breakpoint() или set_trace(). Для подагентов VibeOS отлаживайте по одному процессу за раз.

Контрольный список проверки​

  • После pip install debugpy проверьте: python -c "import debugpy; print(debugpy.__version__)"
  • Для удалённой отладки проверьте, что порт действительно слушает: ss -tlnp | grep 5678
  • Первая точка останова действительно срабатывает (если нет, вероятно, у вас PYTHONBREAKPOINT=0, вы под xdist или выполнение завершилось до подключения)
  • where / w показывает ожидаемый стек вызовов
  • Очистка после отладки: никаких забытых breakpoint() / set_trace() в закоммиченном коде
    rg -n 'breakpoint\(\)|set_trace\(|debugpy\.listen' --type py

Одноразовые рецепты​

«Почему в этом словаре отсутствует ключ?»

# добавьте перед местом возникновения KeyError
breakpoint()
# затем в pdb:
(Pdb) pp d
(Pdb) pp list(d.keys())
(Pdb) w # как мы сюда попали

«Этот тест проходит изолированно, но падает в наборе.»

scripts/run_tests.sh tests/the_test.py --pdb -p no:xdist
# Но если он падает ТОЛЬКО с другими тестами:
source .venv/bin/activate
python -m pytest tests/ -x --pdb -p no:xdist
# Теперь pdb перехватит именно тот тест, который падает, после накопления состояния.

«Мой асинхронный обработчик зависает.»

# Добавьте на входе в обработчик
import remote_pdb; remote_pdb.set_trace(host="127.0.0.1", port=4444)

Вызовите обработчик. nc 127.0.0.1 4444, затем w чтобы увидеть приостановленный фрейм, !import asyncio; asyncio.all_tasks() чтобы увидеть, что ещё ожидает.

«Посмертный анализ сбоя в дочернем процессе Ink / подпроцессе.»

PYTHONFAULTHANDLER=1 python -m pdb -c continue path/to/entrypoint.py
# При сбое pdb окажется в фрейме исключения со всеми локальными переменными