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 | показать исходный код вокруг текущей строки / всей функции |
w | where (стек вызовов) |
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.
Частые ошибки
-
pdb под pytest-xdist молча ничего не делает. Вы не увидите приглашения, тест просто зависнет. Всегда используйте
-p no:xdistили-n 0. -
breakpoint()в CI / не в TTY-контексте вешает процесс. Безопасно только локально; никогда не коммитьте. Добавьте pre-commit grep как страховку. -
PYTHONBREAKPOINT=0отключает все вызовыbreakpoint(). Проверьте переменную окружения, если ваша точка останова не срабатывает:echo $PYTHONBREAKPOINT -
debugpy.listenблокируется только если вы также вызываетеwait_for_client(). Без неё выполнение продолжается, и ваша первая точка останова может сработать до подключения клиента. -
Подключение к PID не работает на усиленных ядрах.
ptrace_scope=1(по умолчанию в Ubuntu) разрешает ptrace только дочерних процессов того же пользователя. Обход:echo 0 > /proc/sys/kernel/yama/ptrace_scope(требует root) или запуск подdebugpyс самого начала. -
Потоки.
pdbотлаживает только текущий поток. Для многопоточного кода используйтеdebugpy(DAP с поддержкой потоков) или установитеthreading.settrace()для каждого потока. -
asyncio.
pdbработает в корутинах, ноawaitвнутри pdb требует Python 3.13+ илиawaitиз режимаinteractна старых версиях. Для 3.11/3.12 используйте трюки сasyncio.run_coroutine_threadsafeили!stmt-основанные await черезasyncio.ensure_future. -
scripts/run_tests.shудаляет учётные данные и устанавливаетHOME=<tmpdir>. Если ваша ошибка зависит от конфигурации пользователя или реальных API-ключей, она не воспроизведётся под обёрткой. Сначала отлаживайте с помощью чистогоpytestдля воспроизведения, затем перепроверьте под обёрткой. -
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 окажется в фрейме исключения со всеми локальными переменными