python-debugpy

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Python Debugger (pdb + debugpy)

Python调试器(pdb + debugpy)

Overview

概述

Three tools, picked by situation:
ToolWhen
breakpoint()
+ pdb
Local, interactive, simplest. Add
breakpoint()
in the source, run normally, get a REPL at that line.
python -m pdb
Launch an existing script under pdb with no source edits. Useful for quick poking.
debugpy
Remote / headless / "attach to already-running process." Talks DAP, scriptable from terminal, works for long-lived processes (gateway, daemon, PTY children).
Start with
breakpoint()
.
It's the cheapest thing that works.
根据场景选择以下三种工具:
工具使用场景
breakpoint()
+ pdb
本地调试、交互式、最简单。在源代码中添加
breakpoint()
,正常运行代码,会在该行进入REPL环境。
python -m pdb
无需修改源代码,直接在pdb环境下启动现有脚本。适合快速排查问题。
debugpy
远程/无头模式/“附加到已运行进程”。支持DAP协议,可通过终端脚本控制,适用于长期运行的进程(网关、守护进程、PTY子进程)。
优先使用
breakpoint()
,这是最便捷有效的方式。

When to Use

适用场景

  • A test fails and the traceback doesn't reveal why a value is wrong
  • You need to step through a function and watch a collection mutate
  • A long-running process (hermes gateway, tui_gateway) misbehaves and you can't restart it
  • Post-mortem: an exception fired in prod-ish code and you want to inspect locals at the crash site
  • A subprocess / child (Python
    _SlashWorker
    , PTY bridge worker) is the actual bug site
Don't use for: things
print()
/
logging.debug
solve in under a minute, or things
pytest -vv --tb=long --showlocals
already reveals.
  • 测试失败,但回溯信息无法揭示值错误的原因
  • 需要单步执行函数,观察集合的变化过程
  • 长期运行的进程(Hermes网关、tui_gateway)出现异常,且无法重启
  • 事后调试:生产环境代码抛出异常,需要检查崩溃现场的局部变量
  • 子进程/子任务(Python
    _SlashWorker
    、PTY桥接工作进程)是实际的问题源头
不适用场景:使用
print()
/
logging.debug
可在一分钟内解决的问题,或
pytest -vv --tb=long --showlocals
已能揭示的问题。

pdb Quick Reference

pdb快速参考

Inside any pdb prompt (
(Pdb)
):
CommandAction
h
/
h cmd
help
n
next line (step over)
s
step into
r
return from current function
c
continue
unt N
continue until line N
j N
jump to line N (same function only)
l
/
ll
list source around current line / full function
w
where (stack trace)
u
/
d
move up / down in the stack
a
print args of the current function
p expr
/
pp expr
print / pretty-print expression
display expr
auto-print expr on every stop
b file:line
set breakpoint
b func
break on function entry
b file:line, cond
conditional breakpoint
cl N
clear breakpoint N
tbreak file:line
one-shot breakpoint
!stmt
execute arbitrary Python (assignments included)
interact
drop into full Python REPL in current scope (Ctrl+D to exit)
q
quit
The
interact
command is the most powerful — you can import anything, inspect complex objects, even call methods that mutate state. Locals are read-only by default; use
!x = 42
from the
(Pdb)
prompt to mutate.
在任意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
进入当前作用域的完整Python REPL环境(按Ctrl+D退出)
q
退出pdb
interact
命令是最强大的功能——你可以导入任意模块、检查复杂对象,甚至调用修改状态的方法。局部变量默认只读;若要修改,需在
(Pdb)
提示符下使用
!x = 42
这样的语法。

Recipe 1: Local breakpoint

方案1:本地断点

Easiest. Edit the file:
python
def compute(x, y):
    result = some_helper(x)
    breakpoint()           # <-- drops into pdb here
    return result + y
Run the code normally. You land at the
breakpoint()
line with full access to locals.
Don't forget to remove
breakpoint()
before committing.
Use
git diff
or a pre-commit grep:
bash
rg -n 'breakpoint\(\)' --type py
最简单的方式。修改代码文件:
python
def compute(x, y):
    result = some_helper(x)
    breakpoint()           # <-- 在此处进入pdb环境
    return result + y
正常运行代码。你会在
breakpoint()
所在行进入pdb环境,并可完全访问局部变量。
提交代码前别忘了移除
breakpoint()
。可以使用
git diff
或预提交的grep检查:
bash
rg -n 'breakpoint\(\)' --type py

Recipe 2: Launch a script under pdb (no source edits)

方案2:在pdb环境下启动脚本(无需修改源代码)

bash
python -m pdb path/to/script.py arg1 arg2
bash
python -m pdb path/to/script.py arg1 arg2

Lands at first line of script

进入脚本的第一行

(Pdb) b path/to/script.py:42 (Pdb) c
undefined
(Pdb) b path/to/script.py:42 (Pdb) c
undefined

Recipe 3: Debug a pytest test

方案3:调试pytest测试用例

The hermes test runner and pytest both support this:
bash
undefined
Hermes测试运行器和pytest均支持以下方式:
bash
undefined

Drop to pdb on failure (or on any raised exception):

测试失败时(或抛出任意异常时)进入pdb:

scripts/run_tests.sh tests/path/to/test_file.py::test_name --pdb
scripts/run_tests.sh tests/path/to/test_file.py::test_name --pdb

Drop to pdb at the START of the test:

在测试开始时进入pdb:

scripts/run_tests.sh tests/path/to/test_file.py::test_name --trace
scripts/run_tests.sh tests/path/to/test_file.py::test_name --trace

Show locals in tracebacks without pdb:

在回溯信息中显示局部变量,无需进入pdb:

scripts/run_tests.sh tests/path/to/test_file.py --showlocals --tb=long

Note: `scripts/run_tests.sh` uses xdist (`-n 4`) by default, and pdb does NOT work under xdist. Add `-p no:xdist` or run a single test with `-n 0`:

```bash
scripts/run_tests.sh tests/foo_test.py::test_bar --pdb -p no:xdist
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`运行单个测试:

```bash
scripts/run_tests.sh tests/foo_test.py::test_bar --pdb -p no:xdist

or

或者

source .venv/bin/activate python -m pytest tests/foo_test.py::test_bar --pdb

This bypasses the hermetic-env guarantees — fine for debugging, but re-run under the wrapper to confirm before pushing.
source .venv/bin/activate python -m pytest tests/foo_test.py::test_bar --pdb

这种方式会绕过封闭环境的保证——调试时没问题,但推送代码前需通过脚本重新运行测试确认。

Recipe 4: Post-mortem on any exception

方案4:针对任意异常的事后调试

python
import pdb, sys
try:
    run_the_thing()
except Exception:
    pdb.post_mortem(sys.exc_info()[2])
Or wrap a whole script:
bash
python -m pdb -c continue script.py
python
import pdb, sys
try:
    run_the_thing()
except Exception:
    pdb.post_mortem(sys.exc_info()[2])
或者为整个脚本添加包装:
bash
python -m pdb -c continue script.py

When it crashes, pdb catches it and you're in the frame of the exception

代码崩溃时,pdb会捕获异常并进入异常所在的栈帧


Or set a global hook in a repl/jupyter:

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

或者在REPL/Jupyter中设置全局钩子:

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

Recipe 5: Remote debug with debugpy (attach to running process)

方案5:使用debugpy进行远程调试(附加到已运行进程)

For long-lived processes: Hermes gateway, tui_gateway, a daemon, a process that's already misbehaving and can't be restarted clean.
适用于长期运行的进程:Hermes网关、tui_gateway、守护进程,或已出现异常且无法正常重启的进程。

Setup

安装配置

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

Pattern A: Source-edit — process waits for debugger at launch

模式A:修改源代码——进程启动时等待调试器连接

Add near the top of the entry point (or inside the function you want to debug):
python
import debugpy
debugpy.listen(("127.0.0.1", 5678))
print("debugpy listening on 5678, waiting for client...", flush=True)
debugpy.wait_for_client()
debugpy.breakpoint()       # optional: pause immediately once attached
Start the process; it blocks on
wait_for_client()
.
在入口文件顶部(或需要调试的函数内部)添加以下代码:
python
import debugpy
debugpy.listen(("127.0.0.1", 5678))
print("debugpy正在监听5678端口,等待客户端连接...", flush=True)
debugpy.wait_for_client()
debugpy.breakpoint()       # 可选:连接成功后立即暂停
启动进程;进程会在
wait_for_client()
处阻塞,等待调试器连接。

Pattern B: No source edit — launch with
-m debugpy

模式B:无需修改源代码——使用
-m debugpy
启动

bash
python -m debugpy --listen 127.0.0.1:5678 --wait-for-client your_script.py arg1
Equivalent for module entry:
bash
python -m debugpy --listen 127.0.0.1:5678 --wait-for-client -m your.module
bash
python -m debugpy --listen 127.0.0.1:5678 --wait-for-client your_script.py arg1
针对模块入口的等效命令:
bash
python -m debugpy --listen 127.0.0.1:5678 --wait-for-client -m your.module

Pattern C: Attach to an already-running process

模式C:附加到已运行的进程

Needs the PID and debugpy preinstalled in the target's environment:
bash
python -m debugpy --listen 127.0.0.1:5678 --pid <pid>
需要目标进程的PID,且目标环境已预安装debugpy:
bash
python -m debugpy --listen 127.0.0.1:5678 --pid <pid>

debugpy injects itself into the process. Then attach a client as below.

debugpy会注入到目标进程中。之后按以下方式连接客户端。


Some kernels/security configs block the ptrace-based injection (`/proc/sys/kernel/yama/ptrace_scope`). Fix with:
```bash
echo 0 | sudo tee /proc/sys/kernel/yama/ptrace_scope

部分内核/安全配置会阻止基于ptrace的注入(`/proc/sys/kernel/yama/ptrace_scope`)。可通过以下命令修复:
```bash
echo 0 | sudo tee /proc/sys/kernel/yama/ptrace_scope

Connecting a client from the terminal

从终端连接客户端

The easiest terminal-side DAP client is VS Code CLI or a small script. From inside Hermes you have two practical options:
Option 1:
debugpy
's own CLI REPL
— not an official feature, but a tiny DAP client script:
python
undefined
最简单的终端端DAP客户端是VS Code CLI或小型脚本。在Hermes环境中,你有两种实用选择:
选项1:
debugpy
自带的CLI REPL
——这并非官方功能,但可通过一个小型DAP客户端脚本实现:
python
undefined

/tmp/dap_client.py

/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"})
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"})

... loop reading events and sending continue/stepIn/etc.

... 循环读取事件并发送continue/stepIn等命令


This is fine for one-off automation but painful as an interactive UX.

**Option 2: Attach from VS Code / Cursor / Zed** — if the user has one open, they can add a `launch.json`:

```json
{
  "name": "Attach to Hermes",
  "type": "debugpy",
  "request": "attach",
  "connect": { "host": "127.0.0.1", "port": 5678 },
  "justMyCode": false,
  "pathMappings": [
    { "localRoot": "${workspaceFolder}", "remoteRoot": "/home/bb/hermes-agent" }
  ]
}
Option 3: Ditch DAP, use
remote-pdb
— usually what you actually want from a terminal agent:
bash
pip install remote-pdb
In your code:
python
from remote_pdb import set_trace
set_trace(host="127.0.0.1", port=4444)   # blocks until connection
Then from the terminal:
bash
nc 127.0.0.1 4444

这种方式适合一次性自动化操作,但作为交互式体验并不友好。

**选项2:从VS Code / Cursor / Zed连接**——如果已打开这些编辑器,可添加`launch.json`配置:

```json
{
  "name": "Attach to Hermes",
  "type": "debugpy",
  "request": "attach",
  "connect": { "host": "127.0.0.1", "port": 5678 },
  "justMyCode": false,
  "pathMappings": [
    { "localRoot": "${workspaceFolder}", "remoteRoot": "/home/bb/hermes-agent" }
  ]
}
选项3:放弃DAP,使用
remote-pdb
——通常这是终端环境下更实用的选择:
bash
pip install remote-pdb
在代码中添加:
python
from remote_pdb import set_trace
set_trace(host="127.0.0.1", port=4444)   # 阻塞直到连接建立
然后在终端中执行:
bash
nc 127.0.0.1 4444

You get a (Pdb) prompt exactly as if debugging locally.

你会获得一个与本地调试完全相同的(Pdb)提示符。


`remote-pdb` is the cleanest agent-friendly choice when `debugpy`'s DAP protocol is overkill. Use `debugpy` only when you actually need IDE integration.

当`debugpy`的DAP协议过于复杂时,`remote-pdb`是终端环境下最简洁的选择。仅当需要IDE集成时才使用`debugpy`。

Debugging Hermes-specific Processes

调试Hermes特定进程

Tests

测试用例

See Recipe 3. Always add
-p no:xdist
or run single tests without xdist.
参考方案3。务必添加
-p no:xdist
或在无xdist的情况下运行单个测试。

run_agent.py
/ CLI — one-shot

run_agent.py
/ CLI —— 一次性进程

Easiest: add
breakpoint()
near the suspect line, then run
hermes
normally. Control returns to your terminal at the pause point.
最简单的方式:在可疑代码行附近添加
breakpoint()
,然后正常运行
hermes
。暂停时控制权会返回至终端。

tui_gateway
subprocess (spawned by
hermes --tui
)

tui_gateway
子进程(由
hermes --tui
启动)

The gateway runs as a child of the Node TUI. Options:
A. Source-edit the gateway:
python
undefined
网关作为Node TUI的子进程运行。可选方案:
A. 修改网关源代码:
python
undefined

tui_gateway/server.py near the top of serve()

tui_gateway/server.py中serve()函数的顶部

import debugpy debugpy.listen(("127.0.0.1", 5678)) debugpy.wait_for_client()
Start `hermes --tui`. The TUI will appear frozen (its backend is waiting). Attach a client; execution resumes when you `continue`.

**B. Use `remote-pdb` at a specific handler:**
```python
from remote_pdb import set_trace
set_trace(host="127.0.0.1", port=4444)   # in the RPC handler you want to trap
Trigger the matching slash command from the TUI, then
nc 127.0.0.1 4444
in another terminal.
import debugpy debugpy.listen(("127.0.0.1", 5678)) debugpy.wait_for_client()
启动`hermes --tui`。TUI会显示为冻结状态(其后端正在等待连接)。连接客户端后,执行`continue`即可恢复运行。

**B. 在特定处理器中使用`remote-pdb`:**
```python
from remote_pdb import set_trace
set_trace(host="127.0.0.1", port=4444)   # 在需要捕获的RPC处理器中添加
从TUI触发对应的slash命令,然后在另一个终端中执行
nc 127.0.0.1 4444

_SlashWorker
subprocess

_SlashWorker
子进程

Same pattern —
remote-pdb
with
set_trace()
inside the worker's
exec
path. The worker is persistent across slash commands, so the first trigger blocks until you connect; subsequent slash commands pass through normally unless you re-arm.
采用相同模式——在工作进程的
exec
路径中添加
remote-pdb
set_trace()
。工作进程会在slash命令之间保持持久化,因此第一次触发时会阻塞直到连接建立;后续slash命令会正常执行,除非重新设置断点。

Gateway (
gateway/run.py
)

网关(
gateway/run.py

Long-lived. Use
remote-pdb
at a handler, or
debugpy
with
--wait-for-client
if you're restarting the gateway anyway.
长期运行的进程。可在处理器中使用
remote-pdb
,或如果需要重启网关,可使用带
--wait-for-client
参数的
debugpy

Common Pitfalls

常见陷阱

  1. pdb under pytest-xdist silently does nothing. You won't see the prompt, the test just hangs. Always use
    -p no:xdist
    or
    -n 0
    .
  2. breakpoint()
    in CI / non-TTY contexts hangs the process.
    Safe locally; never commit it. Add a pre-commit grep as a safety net.
  3. PYTHONBREAKPOINT=0
    disables all
    breakpoint()
    calls. Check the env if your breakpoint isn't hitting:
    bash
    echo $PYTHONBREAKPOINT
  4. debugpy.listen
    blocks only if you also call
    wait_for_client()
    .
    Without it, execution continues and your first breakpoint may fire before the client is attached.
  5. Attach to PID fails on hardened kernels.
    ptrace_scope=1
    (Ubuntu default) allows only same-user ptrace of child processes. Workaround:
    echo 0 > /proc/sys/kernel/yama/ptrace_scope
    (needs root) or launch under
    debugpy
    from the start.
  6. Threads.
    pdb
    only debugs the current thread. For multithreaded code, use
    debugpy
    (thread-aware DAP) or set
    threading.settrace()
    per thread.
  7. asyncio.
    pdb
    works in coroutines but
    await
    inside pdb requires Python 3.13+ or
    await
    from
    interact
    mode on older versions. For 3.11/3.12, use
    asyncio.run_coroutine_threadsafe
    tricks or
    !stmt
    -based awaits via
    asyncio.ensure_future
    .
  8. scripts/run_tests.sh
    strips credentials and sets
    HOME=<tmpdir>
    .
    If your bug depends on user config or real API keys, it won't reproduce under the wrapper. Debug with raw
    pytest
    first to repro, then re-confirm under the wrapper.
  9. Forking / multiprocessing. pdb does not follow forks. Each child needs its own
    breakpoint()
    or
    set_trace()
    . For Hermes subagents, debug one process at a time.
  1. pdb在pytest-xdist环境下会静默失效。你看不到提示符,测试只会挂起。务必使用
    -p no:xdist
    -n 0
  2. 在CI/非TTY环境中使用
    breakpoint()
    会导致进程挂起
    。本地使用安全;绝对不要提交包含
    breakpoint()
    的代码。可添加预提交的grep作为安全措施。
  3. **
    PYTHONBREAKPOINT=0
    **会禁用所有
    breakpoint()
    调用。如果断点未触发,请检查环境变量:
    bash
    echo $PYTHONBREAKPOINT
  4. debugpy.listen
    仅在调用
    wait_for_client()
    时才会阻塞
    。如果不调用该函数,代码会继续执行,你的第一个断点可能在客户端连接前就已触发。
  5. 在加固的内核上附加到PID会失败
    ptrace_scope=1
    (Ubuntu默认设置)仅允许同一用户跟踪其子进程。解决方法:
    echo 0 > /proc/sys/kernel/yama/ptrace_scope
    (需要root权限),或从一开始就使用
    debugpy
    启动进程。
  6. 线程
    pdb
    仅调试当前线程。对于多线程代码,使用
    debugpy
    (支持线程的DAP)或为每个线程设置
    threading.settrace()
  7. asyncio
    pdb
    可在协程中工作,但在pdb中使用
    await
    需要Python 3.13+或在旧版本中从
    interact
    模式执行
    await
    。对于3.11/3.12版本,可使用
    asyncio.run_coroutine_threadsafe
    技巧或通过
    !stmt
    语法结合
    asyncio.ensure_future
    实现
    await
  8. scripts/run_tests.sh
    会清除凭证并设置
    HOME=<tmpdir>
    。如果你的问题依赖于用户配置或真实API密钥,在脚本环境下无法复现。先使用原生
    pytest
    调试复现问题,再通过脚本重新运行确认。
  9. fork/多进程。pdb不会跟随fork进程。每个子进程需要单独设置
    breakpoint()
    set_trace()
    。对于Hermes子代理,一次仅调试一个进程。

Verification Checklist

验证检查清单

  • After
    pip install debugpy
    , confirm:
    python -c "import debugpy; print(debugpy.__version__)"
  • For remote debug, confirm the port is actually listening:
    ss -tlnp | grep 5678
  • First breakpoint actually hits (if it doesn't, you likely have
    PYTHONBREAKPOINT=0
    , you're under xdist, or execution finished before attach)
  • where
    /
    w
    shows the expected call stack
  • Post-debug cleanup: no stray
    breakpoint()
    /
    set_trace()
    in committed code
    bash
    rg -n 'breakpoint\(\)|set_trace\(|debugpy\.listen' --type py
  • 安装debugpy后,确认版本:
    python -c "import debugpy; print(debugpy.__version__)"
  • 远程调试时,确认端口正在监听:
    ss -tlnp | grep 5678
  • 第一个断点确实触发(如果未触发,可能是
    PYTHONBREAKPOINT=0
    、处于xdist环境,或在连接前代码已执行完毕)
  • where
    /
    w
    显示预期的调用栈
  • 调试完成后清理:提交的代码中无残留的
    breakpoint()
    /
    set_trace()
    bash
    rg -n 'breakpoint\(\)|set_trace\(|debugpy\.listen' --type py

One-Shot Recipes

快速解决方案

"Why is this dict missing a key?"
python
undefined
“为什么这个字典缺少某个键?”
python
undefined

add above the KeyError site

在KeyError出现位置上方添加

breakpoint()
breakpoint()

then in pdb:

然后在pdb中执行:

(Pdb) pp d (Pdb) pp list(d.keys()) (Pdb) w # how did we get here

**"This test passes in isolation but fails in the suite."**
```bash
scripts/run_tests.sh tests/the_test.py --pdb -p no:xdist
(Pdb) pp d (Pdb) pp list(d.keys()) (Pdb) w # 查看调用路径

**“这个测试单独运行通过,但在测试套件中失败。”**
```bash
scripts/run_tests.sh tests/the_test.py --pdb -p no:xdist

But if it only fails WITH other tests:

如果仅在与其他测试一起运行时失败:

source .venv/bin/activate python -m pytest tests/ -x --pdb -p no:xdist
source .venv/bin/activate python -m pytest tests/ -x --pdb -p no:xdist

Now it pdb-traps at the exact failing test after state accumulated.

现在pdb会在状态累积后,在第一个失败的测试处触发。


**"My async handler deadlocks."**
```python

**“我的异步处理器死锁了。”**
```python

Add at handler entry

在处理器入口添加

import remote_pdb; remote_pdb.set_trace(host="127.0.0.1", port=4444)
Trigger the handler. `nc 127.0.0.1 4444`, then `w` to see the suspended frame, `!import asyncio; asyncio.all_tasks()` to see what else is pending.

**"Post-mortem on a crash in an Ink child process / subprocess."**
```bash
PYTHONFAULTHANDLER=1 python -m pdb -c continue path/to/entrypoint.py
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子进程/子进程的崩溃进行事后调试。”**
```bash
PYTHONFAULTHANDLER=1 python -m pdb -c continue path/to/entrypoint.py

On crash, pdb lands at the frame of the exception with full locals

崩溃时,pdb会进入异常所在的栈帧,并显示完整的局部变量

undefined
undefined