[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M71J2TTDMFVR3N363AQ4X9K9":3,"skill-same:01M71J2TTDMFVR3N363AQ4X9K9":77},{"skillNo":4,"name":5,"nameZh":6,"description":7,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":15,"categories":16,"keywords":22,"publishedAt":43,"linkOnly":44,"markdownContent":45,"markdownBody":46,"frontMatter":47,"refName":56,"commitSha":57,"skillDirectory":58,"forksCount":59,"repositoryHtmlUrl":60,"repositoryHomepageUrl":6,"ownerHtmlUrl":61,"ownerBlogUrl":6,"ownerDescription":6,"updatedAt":62,"install":63,"resources":76},"01M71J2TTDMFVR3N363AQ4X9K9","python-debugpy",null,"Debug Python: pdb REPL + debugpy remote (DAP).","0xzgbot\u002Fforge-nps-v01","0xzgbot","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F271900281?v=4","01M71GXWAFRXXJSJE3HBCVDKZY","01M71GXWBFQYRJK7M8RM6BDP0X",6,"MIT","https:\u002F\u002Fgithub.com\u002F0xzgbot\u002Fforge-nps-v01\u002Fblob\u002Fmaster\u002Fhermes_home\u002Fskills\u002Fsoftware-development\u002Fpython-debugpy\u002FSKILL.md",[17],{"categoryNo":18,"categoryName":19,"taxonomyType":20,"taxonomyTypeDesc":21},"01JBSEEDCAT000000000000030","开发工程师","PROFESSION","适用职业",[23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42],"python debugpy","python","debugpy","tui","hermes","node-inspect","breakpoints","post","debug","systematic-debugging","commands","node","debugger","systematic","debugging","remote","pdb","post-mortem","inspect","dap","2026-11-10 10:12:30",false,"---\nname: python-debugpy\ndescription: \"Debug Python: pdb REPL + debugpy remote (DAP).\"\nversion: 1.0.0\nauthor: Hermes Agent\nlicense: MIT\nmetadata:\n  hermes:\n    tags: [debugging, python, pdb, debugpy, breakpoints, dap, post-mortem]\n    related_skills: [systematic-debugging, node-inspect-debugger, debugging-hermes-tui-commands]\n---\n\n# Python Debugger (pdb + debugpy)\n\n## Overview\n\nThree tools, picked by situation:\n\n| Tool | When |\n|---|---|\n| **`breakpoint()` + pdb** | Local, interactive, simplest. Add `breakpoint()` in the source, run normally, get a REPL at that line. |\n| **`python -m pdb`** | Launch an existing script under pdb with no source edits. Useful for quick poking. |\n| **`debugpy`** | Remote \u002F headless \u002F \"attach to already-running process.\" Talks DAP, scriptable from terminal, works for long-lived processes (gateway, daemon, PTY children). |\n\n**Start with `breakpoint()`.** It's the cheapest thing that works.\n\n## When to Use\n\n- A test fails and the traceback doesn't reveal why a value is wrong\n- You need to step through a function and watch a collection mutate\n- A long-running process (hermes gateway, tui_gateway) misbehaves and you can't restart it\n- Post-mortem: an exception fired in prod-ish code and you want to inspect locals at the crash site\n- A subprocess \u002F child (Python `_SlashWorker`, PTY bridge worker) is the actual bug site\n\n**Don't use for:** things `print()` \u002F `logging.debug` solve in under a minute, or things `pytest -vv --tb=long --showlocals` already reveals.\n\n## pdb Quick Reference\n\nInside any pdb prompt (`(Pdb)`):\n\n| Command | Action |\n|---|---|\n| `h` \u002F `h cmd` | help |\n| `n` | next line (step over) |\n| `s` | step into |\n| `r` | return from current function |\n| `c` | continue |\n| `unt N` | continue until line N |\n| `j N` | jump to line N (same function only) |\n| `l` \u002F `ll` | list source around current line \u002F full function |\n| `w` | where (stack trace) |\n| `u` \u002F `d` | move up \u002F down in the stack |\n| `a` | print args of the current function |\n| `p expr` \u002F `pp expr` | print \u002F pretty-print expression |\n| `display expr` | auto-print expr on every stop |\n| `b file:line` | set breakpoint |\n| `b func` | break on function entry |\n| `b file:line, cond` | conditional breakpoint |\n| `cl N` | clear breakpoint N |\n| `tbreak file:line` | one-shot breakpoint |\n| `!stmt` | execute arbitrary Python (assignments included) |\n| `interact` | drop into full Python REPL in current scope (Ctrl+D to exit) |\n| `q` | quit |\n\nThe `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.\n\n## Recipe 1: Local breakpoint\n\nEasiest. Edit the file:\n\n```python\ndef compute(x, y):\n    result = some_helper(x)\n    breakpoint()           # \u003C-- drops into pdb here\n    return result + y\n```\n\nRun the code normally. You land at the `breakpoint()` line with full access to locals.\n\n**Don't forget to remove `breakpoint()` before committing.** Use `git diff` or a pre-commit grep:\n```bash\nrg -n 'breakpoint\\(\\)' --type py\n```\n\n## Recipe 2: Launch a script under pdb (no source edits)\n\n```bash\npython -m pdb path\u002Fto\u002Fscript.py arg1 arg2\n# Lands at first line of script\n(Pdb) b path\u002Fto\u002Fscript.py:42\n(Pdb) c\n```\n\n## Recipe 3: Debug a pytest test\n\nThe hermes test runner and pytest both support this:\n\n```bash\n# Drop to pdb on failure (or on any raised exception):\nscripts\u002Frun_tests.sh tests\u002Fpath\u002Fto\u002Ftest_file.py::test_name --pdb\n\n# Drop to pdb at the START of the test:\nscripts\u002Frun_tests.sh tests\u002Fpath\u002Fto\u002Ftest_file.py::test_name --trace\n\n# Show locals in tracebacks without pdb:\nscripts\u002Frun_tests.sh tests\u002Fpath\u002Fto\u002Ftest_file.py --showlocals --tb=long\n```\n\nNote: `scripts\u002Frun_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`:\n\n```bash\nscripts\u002Frun_tests.sh tests\u002Ffoo_test.py::test_bar --pdb -p no:xdist\n# or\nsource .venv\u002Fbin\u002Factivate\npython -m pytest tests\u002Ffoo_test.py::test_bar --pdb\n```\n\nThis bypasses the hermetic-env guarantees — fine for debugging, but re-run under the wrapper to confirm before pushing.\n\n## Recipe 4: Post-mortem on any exception\n\n```python\nimport pdb, sys\ntry:\n    run_the_thing()\nexcept Exception:\n    pdb.post_mortem(sys.exc_info()[2])\n```\n\nOr wrap a whole script:\n\n```bash\npython -m pdb -c continue script.py\n# When it crashes, pdb catches it and you're in the frame of the exception\n```\n\nOr set a global hook in a repl\u002Fjupyter:\n\n```python\nimport sys\ndef excepthook(etype, value, tb):\n    import pdb; pdb.post_mortem(tb)\nsys.excepthook = excepthook\n```\n\n## Recipe 5: Remote debug with debugpy (attach to running process)\n\nFor long-lived processes: Hermes gateway, tui_gateway, a daemon, a process that's already misbehaving and can't be restarted clean.\n\n### Setup\n\n```bash\nsource \u002Fhome\u002Fbb\u002Fhermes-agent\u002F.venv\u002Fbin\u002Factivate\npip install debugpy\n```\n\n### Pattern A: Source-edit — process waits for debugger at launch\n\nAdd near the top of the entry point (or inside the function you want to debug):\n\n```python\nimport debugpy\ndebugpy.listen((\"127.0.0.1\", 5678))\nprint(\"debugpy listening on 5678, waiting for client...\", flush=True)\ndebugpy.wait_for_client()\ndebugpy.breakpoint()       # optional: pause immediately once attached\n```\n\nStart the process; it blocks on `wait_for_client()`.\n\n### Pattern B: No source edit — launch with `-m debugpy`\n\n```bash\npython -m debugpy --listen 127.0.0.1:5678 --wait-for-client your_script.py arg1\n```\n\nEquivalent for module entry:\n\n```bash\npython -m debugpy --listen 127.0.0.1:5678 --wait-for-client -m your.module\n```\n\n### Pattern C: Attach to an already-running process\n\nNeeds the PID and debugpy preinstalled in the target's environment:\n\n```bash\npython -m debugpy --listen 127.0.0.1:5678 --pid \u003Cpid>\n# debugpy injects itself into the process. Then attach a client as below.\n```\n\nSome kernels\u002Fsecurity configs block the ptrace-based injection (`\u002Fproc\u002Fsys\u002Fkernel\u002Fyama\u002Fptrace_scope`). Fix with:\n```bash\necho 0 | sudo tee \u002Fproc\u002Fsys\u002Fkernel\u002Fyama\u002Fptrace_scope\n```\n\n### Connecting a client from the terminal\n\nThe easiest terminal-side DAP client is VS Code CLI or a small script. From inside Hermes you have two practical options:\n\n**Option 1: `debugpy`'s own CLI REPL** — not an official feature, but a tiny DAP client script:\n\n```python\n# \u002Ftmp\u002Fdap_client.py\nimport socket, json, itertools, time, sys\n\nHOST, PORT = \"127.0.0.1\", 5678\ns = socket.create_connection((HOST, PORT))\nseq = itertools.count(1)\n\ndef send(msg):\n    msg[\"seq\"] = next(seq)\n    body = json.dumps(msg).encode()\n    s.sendall(f\"Content-Length: {len(body)}\\r\\n\\r\\n\".encode() + body)\n\ndef recv():\n    header = b\"\"\n    while b\"\\r\\n\\r\\n\" not in header:\n        header += s.recv(1)\n    length = int(header.decode().split(\"Content-Length:\")[1].split(\"\\r\\n\")[0].strip())\n    body = b\"\"\n    while len(body) \u003C length:\n        body += s.recv(length - len(body))\n    return json.loads(body)\n\nsend({\"type\": \"request\", \"command\": \"initialize\", \"arguments\": {\"adapterID\": \"python\"}})\nprint(recv())\nsend({\"type\": \"request\", \"command\": \"attach\", \"arguments\": {}})\nprint(recv())\nsend({\"type\": \"request\", \"command\": \"setBreakpoints\",\n      \"arguments\": {\"source\": {\"path\": sys.argv[1]},\n                    \"breakpoints\": [{\"line\": int(sys.argv[2])}]}})\nprint(recv())\nsend({\"type\": \"request\", \"command\": \"configurationDone\"})\n# ... loop reading events and sending continue\u002FstepIn\u002Fetc.\n```\n\nThis is fine for one-off automation but painful as an interactive UX.\n\n**Option 2: Attach from VS Code \u002F Cursor \u002F Zed** — if the user has one open, they can add a `launch.json`:\n\n```json\n{\n  \"name\": \"Attach to Hermes\",\n  \"type\": \"debugpy\",\n  \"request\": \"attach\",\n  \"connect\": { \"host\": \"127.0.0.1\", \"port\": 5678 },\n  \"justMyCode\": false,\n  \"pathMappings\": [\n    { \"localRoot\": \"${workspaceFolder}\", \"remoteRoot\": \"\u002Fhome\u002Fbb\u002Fhermes-agent\" }\n  ]\n}\n```\n\n**Option 3: Ditch DAP, use `remote-pdb`** — usually what you actually want from a terminal agent:\n\n```bash\npip install remote-pdb\n```\n\nIn your code:\n```python\nfrom remote_pdb import set_trace\nset_trace(host=\"127.0.0.1\", port=4444)   # blocks until connection\n```\n\nThen from the terminal:\n```bash\nnc 127.0.0.1 4444\n# You get a (Pdb) prompt exactly as if debugging locally.\n```\n\n`remote-pdb` is the cleanest agent-friendly choice when `debugpy`'s DAP protocol is overkill. Use `debugpy` only when you actually need IDE integration.\n\n## Debugging Hermes-specific Processes\n\n### Tests\nSee Recipe 3. Always add `-p no:xdist` or run single tests without xdist.\n\n### `run_agent.py` \u002F CLI — one-shot\nEasiest: add `breakpoint()` near the suspect line, then run `hermes` normally. Control returns to your terminal at the pause point.\n\n### `tui_gateway` subprocess (spawned by `hermes --tui`)\nThe gateway runs as a child of the Node TUI. Options:\n\n**A. Source-edit the gateway:**\n```python\n# tui_gateway\u002Fserver.py near the top of serve()\nimport debugpy\ndebugpy.listen((\"127.0.0.1\", 5678))\ndebugpy.wait_for_client()\n```\nStart `hermes --tui`. The TUI will appear frozen (its backend is waiting). Attach a client; execution resumes when you `continue`.\n\n**B. Use `remote-pdb` at a specific handler:**\n```python\nfrom remote_pdb import set_trace\nset_trace(host=\"127.0.0.1\", port=4444)   # in the RPC handler you want to trap\n```\nTrigger the matching slash command from the TUI, then `nc 127.0.0.1 4444` in another terminal.\n\n### `_SlashWorker` subprocess\nSame 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.\n\n### Gateway (`gateway\u002Frun.py`)\nLong-lived. Use `remote-pdb` at a handler, or `debugpy` with `--wait-for-client` if you're restarting the gateway anyway.\n\n## Common Pitfalls\n\n1. **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`.\n\n2. **`breakpoint()` in CI \u002F non-TTY contexts hangs the process.** Safe locally; never commit it. Add a pre-commit grep as a safety net.\n\n3. **`PYTHONBREAKPOINT=0`** disables all `breakpoint()` calls. Check the env if your breakpoint isn't hitting:\n   ```bash\n   echo $PYTHONBREAKPOINT\n   ```\n\n4. **`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.\n\n5. **Attach to PID fails on hardened kernels.** `ptrace_scope=1` (Ubuntu default) allows only same-user ptrace of child processes. Workaround: `echo 0 > \u002Fproc\u002Fsys\u002Fkernel\u002Fyama\u002Fptrace_scope` (needs root) or launch under `debugpy` from the start.\n\n6. **Threads.** `pdb` only debugs the current thread. For multithreaded code, use `debugpy` (thread-aware DAP) or set `threading.settrace()` per thread.\n\n7. **asyncio.** `pdb` works in coroutines but `await` inside pdb requires Python 3.13+ or `await` from `interact` mode on older versions. For 3.11\u002F3.12, use `asyncio.run_coroutine_threadsafe` tricks or `!stmt`-based awaits via `asyncio.ensure_future`.\n\n8. **`scripts\u002Frun_tests.sh` strips credentials and sets `HOME=\u003Ctmpdir>`.** 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.\n\n9. **Forking \u002F multiprocessing.** pdb does not follow forks. Each child needs its own `breakpoint()` or `set_trace()`. For Hermes subagents, debug one process at a time.\n\n## Verification Checklist\n\n- [ ] After `pip install debugpy`, confirm: `python -c \"import debugpy; print(debugpy.__version__)\"`\n- [ ] For remote debug, confirm the port is actually listening: `ss -tlnp | grep 5678`\n- [ ] First breakpoint actually hits (if it doesn't, you likely have `PYTHONBREAKPOINT=0`, you're under xdist, or execution finished before attach)\n- [ ] `where` \u002F `w` shows the expected call stack\n- [ ] Post-debug cleanup: no stray `breakpoint()` \u002F `set_trace()` in committed code\n  ```bash\n  rg -n 'breakpoint\\(\\)|set_trace\\(|debugpy\\.listen' --type py\n  ```\n\n## One-Shot Recipes\n\n**\"Why is this dict missing a key?\"**\n```python\n# add above the KeyError site\nbreakpoint()\n# then in pdb:\n(Pdb) pp d\n(Pdb) pp list(d.keys())\n(Pdb) w                # how did we get here\n```\n\n**\"This test passes in isolation but fails in the suite.\"**\n```bash\nscripts\u002Frun_tests.sh tests\u002Fthe_test.py --pdb -p no:xdist\n# But if it only fails WITH other tests:\nsource .venv\u002Fbin\u002Factivate\npython -m pytest tests\u002F -x --pdb -p no:xdist\n# Now it pdb-traps at the exact failing test after state accumulated.\n```\n\n**\"My async handler deadlocks.\"**\n```python\n# Add at handler entry\nimport remote_pdb; remote_pdb.set_trace(host=\"127.0.0.1\", port=4444)\n```\nTrigger 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.\n\n**\"Post-mortem on a crash in an Ink child process \u002F subprocess.\"**\n```bash\nPYTHONFAULTHANDLER=1 python -m pdb -c continue path\u002Fto\u002Fentrypoint.py\n# On crash, pdb lands at the frame of the exception with full locals\n```\n","# Python Debugger (pdb + debugpy)\n\n## Overview\n\nThree tools, picked by situation:\n\n| Tool | When |\n|---|---|\n| **`breakpoint()` + pdb** | Local, interactive, simplest. Add `breakpoint()` in the source, run normally, get a REPL at that line. |\n| **`python -m pdb`** | Launch an existing script under pdb with no source edits. Useful for quick poking. |\n| **`debugpy`** | Remote \u002F headless \u002F \"attach to already-running process.\" Talks DAP, scriptable from terminal, works for long-lived processes (gateway, daemon, PTY children). |\n\n**Start with `breakpoint()`.** It's the cheapest thing that works.\n\n## When to Use\n\n- A test fails and the traceback doesn't reveal why a value is wrong\n- You need to step through a function and watch a collection mutate\n- A long-running process (hermes gateway, tui_gateway) misbehaves and you can't restart it\n- Post-mortem: an exception fired in prod-ish code and you want to inspect locals at the crash site\n- A subprocess \u002F child (Python `_SlashWorker`, PTY bridge worker) is the actual bug site\n\n**Don't use for:** things `print()` \u002F `logging.debug` solve in under a minute, or things `pytest -vv --tb=long --showlocals` already reveals.\n\n## pdb Quick Reference\n\nInside any pdb prompt (`(Pdb)`):\n\n| Command | Action |\n|---|---|\n| `h` \u002F `h cmd` | help |\n| `n` | next line (step over) |\n| `s` | step into |\n| `r` | return from current function |\n| `c` | continue |\n| `unt N` | continue until line N |\n| `j N` | jump to line N (same function only) |\n| `l` \u002F `ll` | list source around current line \u002F full function |\n| `w` | where (stack trace) |\n| `u` \u002F `d` | move up \u002F down in the stack |\n| `a` | print args of the current function |\n| `p expr` \u002F `pp expr` | print \u002F pretty-print expression |\n| `display expr` | auto-print expr on every stop |\n| `b file:line` | set breakpoint |\n| `b func` | break on function entry |\n| `b file:line, cond` | conditional breakpoint |\n| `cl N` | clear breakpoint N |\n| `tbreak file:line` | one-shot breakpoint |\n| `!stmt` | execute arbitrary Python (assignments included) |\n| `interact` | drop into full Python REPL in current scope (Ctrl+D to exit) |\n| `q` | quit |\n\nThe `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.\n\n## Recipe 1: Local breakpoint\n\nEasiest. Edit the file:\n\n```python\ndef compute(x, y):\n    result = some_helper(x)\n    breakpoint()           # \u003C-- drops into pdb here\n    return result + y\n```\n\nRun the code normally. You land at the `breakpoint()` line with full access to locals.\n\n**Don't forget to remove `breakpoint()` before committing.** Use `git diff` or a pre-commit grep:\n```bash\nrg -n 'breakpoint\\(\\)' --type py\n```\n\n## Recipe 2: Launch a script under pdb (no source edits)\n\n```bash\npython -m pdb path\u002Fto\u002Fscript.py arg1 arg2\n# Lands at first line of script\n(Pdb) b path\u002Fto\u002Fscript.py:42\n(Pdb) c\n```\n\n## Recipe 3: Debug a pytest test\n\nThe hermes test runner and pytest both support this:\n\n```bash\n# Drop to pdb on failure (or on any raised exception):\nscripts\u002Frun_tests.sh tests\u002Fpath\u002Fto\u002Ftest_file.py::test_name --pdb\n\n# Drop to pdb at the START of the test:\nscripts\u002Frun_tests.sh tests\u002Fpath\u002Fto\u002Ftest_file.py::test_name --trace\n\n# Show locals in tracebacks without pdb:\nscripts\u002Frun_tests.sh tests\u002Fpath\u002Fto\u002Ftest_file.py --showlocals --tb=long\n```\n\nNote: `scripts\u002Frun_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`:\n\n```bash\nscripts\u002Frun_tests.sh tests\u002Ffoo_test.py::test_bar --pdb -p no:xdist\n# or\nsource .venv\u002Fbin\u002Factivate\npython -m pytest tests\u002Ffoo_test.py::test_bar --pdb\n```\n\nThis bypasses the hermetic-env guarantees — fine for debugging, but re-run under the wrapper to confirm before pushing.\n\n## Recipe 4: Post-mortem on any exception\n\n```python\nimport pdb, sys\ntry:\n    run_the_thing()\nexcept Exception:\n    pdb.post_mortem(sys.exc_info()[2])\n```\n\nOr wrap a whole script:\n\n```bash\npython -m pdb -c continue script.py\n# When it crashes, pdb catches it and you're in the frame of the exception\n```\n\nOr set a global hook in a repl\u002Fjupyter:\n\n```python\nimport sys\ndef excepthook(etype, value, tb):\n    import pdb; pdb.post_mortem(tb)\nsys.excepthook = excepthook\n```\n\n## Recipe 5: Remote debug with debugpy (attach to running process)\n\nFor long-lived processes: Hermes gateway, tui_gateway, a daemon, a process that's already misbehaving and can't be restarted clean.\n\n### Setup\n\n```bash\nsource \u002Fhome\u002Fbb\u002Fhermes-agent\u002F.venv\u002Fbin\u002Factivate\npip install debugpy\n```\n\n### Pattern A: Source-edit — process waits for debugger at launch\n\nAdd near the top of the entry point (or inside the function you want to debug):\n\n```python\nimport debugpy\ndebugpy.listen((\"127.0.0.1\", 5678))\nprint(\"debugpy listening on 5678, waiting for client...\", flush=True)\ndebugpy.wait_for_client()\ndebugpy.breakpoint()       # optional: pause immediately once attached\n```\n\nStart the process; it blocks on `wait_for_client()`.\n\n### Pattern B: No source edit — launch with `-m debugpy`\n\n```bash\npython -m debugpy --listen 127.0.0.1:5678 --wait-for-client your_script.py arg1\n```\n\nEquivalent for module entry:\n\n```bash\npython -m debugpy --listen 127.0.0.1:5678 --wait-for-client -m your.module\n```\n\n### Pattern C: Attach to an already-running process\n\nNeeds the PID and debugpy preinstalled in the target's environment:\n\n```bash\npython -m debugpy --listen 127.0.0.1:5678 --pid \u003Cpid>\n# debugpy injects itself into the process. Then attach a client as below.\n```\n\nSome kernels\u002Fsecurity configs block the ptrace-based injection (`\u002Fproc\u002Fsys\u002Fkernel\u002Fyama\u002Fptrace_scope`). Fix with:\n```bash\necho 0 | sudo tee \u002Fproc\u002Fsys\u002Fkernel\u002Fyama\u002Fptrace_scope\n```\n\n### Connecting a client from the terminal\n\nThe easiest terminal-side DAP client is VS Code CLI or a small script. From inside Hermes you have two practical options:\n\n**Option 1: `debugpy`'s own CLI REPL** — not an official feature, but a tiny DAP client script:\n\n```python\n# \u002Ftmp\u002Fdap_client.py\nimport socket, json, itertools, time, sys\n\nHOST, PORT = \"127.0.0.1\", 5678\ns = socket.create_connection((HOST, PORT))\nseq = itertools.count(1)\n\ndef send(msg):\n    msg[\"seq\"] = next(seq)\n    body = json.dumps(msg).encode()\n    s.sendall(f\"Content-Length: {len(body)}\\r\\n\\r\\n\".encode() + body)\n\ndef recv():\n    header = b\"\"\n    while b\"\\r\\n\\r\\n\" not in header:\n        header += s.recv(1)\n    length = int(header.decode().split(\"Content-Length:\")[1].split(\"\\r\\n\")[0].strip())\n    body = b\"\"\n    while len(body) \u003C length:\n        body += s.recv(length - len(body))\n    return json.loads(body)\n\nsend({\"type\": \"request\", \"command\": \"initialize\", \"arguments\": {\"adapterID\": \"python\"}})\nprint(recv())\nsend({\"type\": \"request\", \"command\": \"attach\", \"arguments\": {}})\nprint(recv())\nsend({\"type\": \"request\", \"command\": \"setBreakpoints\",\n      \"arguments\": {\"source\": {\"path\": sys.argv[1]},\n                    \"breakpoints\": [{\"line\": int(sys.argv[2])}]}})\nprint(recv())\nsend({\"type\": \"request\", \"command\": \"configurationDone\"})\n# ... loop reading events and sending continue\u002FstepIn\u002Fetc.\n```\n\nThis is fine for one-off automation but painful as an interactive UX.\n\n**Option 2: Attach from VS Code \u002F Cursor \u002F Zed** — if the user has one open, they can add a `launch.json`:\n\n```json\n{\n  \"name\": \"Attach to Hermes\",\n  \"type\": \"debugpy\",\n  \"request\": \"attach\",\n  \"connect\": { \"host\": \"127.0.0.1\", \"port\": 5678 },\n  \"justMyCode\": false,\n  \"pathMappings\": [\n    { \"localRoot\": \"${workspaceFolder}\", \"remoteRoot\": \"\u002Fhome\u002Fbb\u002Fhermes-agent\" }\n  ]\n}\n```\n\n**Option 3: Ditch DAP, use `remote-pdb`** — usually what you actually want from a terminal agent:\n\n```bash\npip install remote-pdb\n```\n\nIn your code:\n```python\nfrom remote_pdb import set_trace\nset_trace(host=\"127.0.0.1\", port=4444)   # blocks until connection\n```\n\nThen from the terminal:\n```bash\nnc 127.0.0.1 4444\n# You get a (Pdb) prompt exactly as if debugging locally.\n```\n\n`remote-pdb` is the cleanest agent-friendly choice when `debugpy`'s DAP protocol is overkill. Use `debugpy` only when you actually need IDE integration.\n\n## Debugging Hermes-specific Processes\n\n### Tests\nSee Recipe 3. Always add `-p no:xdist` or run single tests without xdist.\n\n### `run_agent.py` \u002F CLI — one-shot\nEasiest: add `breakpoint()` near the suspect line, then run `hermes` normally. Control returns to your terminal at the pause point.\n\n### `tui_gateway` subprocess (spawned by `hermes --tui`)\nThe gateway runs as a child of the Node TUI. Options:\n\n**A. Source-edit the gateway:**\n```python\n# tui_gateway\u002Fserver.py near the top of serve()\nimport debugpy\ndebugpy.listen((\"127.0.0.1\", 5678))\ndebugpy.wait_for_client()\n```\nStart `hermes --tui`. The TUI will appear frozen (its backend is waiting). Attach a client; execution resumes when you `continue`.\n\n**B. Use `remote-pdb` at a specific handler:**\n```python\nfrom remote_pdb import set_trace\nset_trace(host=\"127.0.0.1\", port=4444)   # in the RPC handler you want to trap\n```\nTrigger the matching slash command from the TUI, then `nc 127.0.0.1 4444` in another terminal.\n\n### `_SlashWorker` subprocess\nSame 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.\n\n### Gateway (`gateway\u002Frun.py`)\nLong-lived. Use `remote-pdb` at a handler, or `debugpy` with `--wait-for-client` if you're restarting the gateway anyway.\n\n## Common Pitfalls\n\n1. **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`.\n\n2. **`breakpoint()` in CI \u002F non-TTY contexts hangs the process.** Safe locally; never commit it. Add a pre-commit grep as a safety net.\n\n3. **`PYTHONBREAKPOINT=0`** disables all `breakpoint()` calls. Check the env if your breakpoint isn't hitting:\n   ```bash\n   echo $PYTHONBREAKPOINT\n   ```\n\n4. **`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.\n\n5. **Attach to PID fails on hardened kernels.** `ptrace_scope=1` (Ubuntu default) allows only same-user ptrace of child processes. Workaround: `echo 0 > \u002Fproc\u002Fsys\u002Fkernel\u002Fyama\u002Fptrace_scope` (needs root) or launch under `debugpy` from the start.\n\n6. **Threads.** `pdb` only debugs the current thread. For multithreaded code, use `debugpy` (thread-aware DAP) or set `threading.settrace()` per thread.\n\n7. **asyncio.** `pdb` works in coroutines but `await` inside pdb requires Python 3.13+ or `await` from `interact` mode on older versions. For 3.11\u002F3.12, use `asyncio.run_coroutine_threadsafe` tricks or `!stmt`-based awaits via `asyncio.ensure_future`.\n\n8. **`scripts\u002Frun_tests.sh` strips credentials and sets `HOME=\u003Ctmpdir>`.** 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.\n\n9. **Forking \u002F multiprocessing.** pdb does not follow forks. Each child needs its own `breakpoint()` or `set_trace()`. For Hermes subagents, debug one process at a time.\n\n## Verification Checklist\n\n- [ ] After `pip install debugpy`, confirm: `python -c \"import debugpy; print(debugpy.__version__)\"`\n- [ ] For remote debug, confirm the port is actually listening: `ss -tlnp | grep 5678`\n- [ ] First breakpoint actually hits (if it doesn't, you likely have `PYTHONBREAKPOINT=0`, you're under xdist, or execution finished before attach)\n- [ ] `where` \u002F `w` shows the expected call stack\n- [ ] Post-debug cleanup: no stray `breakpoint()` \u002F `set_trace()` in committed code\n  ```bash\n  rg -n 'breakpoint\\(\\)|set_trace\\(|debugpy\\.listen' --type py\n  ```\n\n## One-Shot Recipes\n\n**\"Why is this dict missing a key?\"**\n```python\n# add above the KeyError site\nbreakpoint()\n# then in pdb:\n(Pdb) pp d\n(Pdb) pp list(d.keys())\n(Pdb) w                # how did we get here\n```\n\n**\"This test passes in isolation but fails in the suite.\"**\n```bash\nscripts\u002Frun_tests.sh tests\u002Fthe_test.py --pdb -p no:xdist\n# But if it only fails WITH other tests:\nsource .venv\u002Fbin\u002Factivate\npython -m pytest tests\u002F -x --pdb -p no:xdist\n# Now it pdb-traps at the exact failing test after state accumulated.\n```\n\n**\"My async handler deadlocks.\"**\n```python\n# Add at handler entry\nimport remote_pdb; remote_pdb.set_trace(host=\"127.0.0.1\", port=4444)\n```\nTrigger 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.\n\n**\"Post-mortem on a crash in an Ink child process \u002F subprocess.\"**\n```bash\nPYTHONFAULTHANDLER=1 python -m pdb -c continue path\u002Fto\u002Fentrypoint.py\n# On crash, pdb lands at the frame of the exception with full locals\n```\n",{"name":5,"author":48,"license":14,"version":49,"metadata":50,"description":7},"Hermes Agent","1.0.0",{"hermes":51},{"tags":52,"related_skills":53},[37,24,39,25,29,42,40],[32,54,55],"node-inspect-debugger","debugging-hermes-tui-commands","master","a76a61826887171cb38ce217ae85643d5d7685d1","hermes_home\u002Fskills\u002Fsoftware-development\u002Fpython-debugpy",2,"https:\u002F\u002Fgithub.com\u002F0xzgbot\u002Fforge-nps-v01","https:\u002F\u002Fgithub.com\u002F0xzgbot","2026-08-31 09:13:28",{"source":8,"skillName":5,"commands":64,"prompt":74,"zipAvailable":75},[65,68,71],{"tool":66,"command":67},"npx","npx skills add 0xzgbot\u002Fforge-nps-v01 --skill python-debugpy",{"tool":69,"command":70},"bunx","bunx skills add 0xzgbot\u002Fforge-nps-v01 --skill python-debugpy",{"tool":72,"command":73},"pnpm","pnpm dlx skills add 0xzgbot\u002Fforge-nps-v01 --skill python-debugpy","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002F0xzgbot\u002Fforge-nps-v01\u002Fblob\u002Fmaster\u002Fhermes_home\u002Fskills\u002Fsoftware-development\u002Fpython-debugpy\u002FSKILL.md",true,[],[78,91,113,127,138,150],{"skillNo":79,"name":80,"nameZh":6,"description":81,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":82,"categories":83,"keywords":84,"publishedAt":43},"01M71J5TFCV7K97773JYZE7ET8","yuanbao","Yuanbao (元宝) groups: @mention users, query info\u002Fmembers.","https:\u002F\u002Fgithub.com\u002F0xzgbot\u002Fforge-nps-v01\u002Fblob\u002Fmaster\u002Fhermes_home\u002Fskills\u002Fyuanbao\u002FSKILL.md",[],[80,85,86,87,88,89,90],"groups","at","info","users","query","group",{"skillNo":92,"name":93,"nameZh":6,"description":94,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":95,"categories":96,"keywords":105,"publishedAt":43},"01M71J5QR69MCT5J8P3B4Y9ZHA","workflow-ltx-i2v","Use when the task involves workflow ltx i2v. DESCRIPTION.","https:\u002F\u002Fgithub.com\u002F0xzgbot\u002Fforge-nps-v01\u002Fblob\u002Fmaster\u002Fhermes_home\u002Fskills\u002Fworkflow_ltx_i2v\u002FSKILL.md",[97,102],{"categoryNo":98,"categoryName":99,"taxonomyType":100,"taxonomyTypeDesc":101},"01JBSEEDCAT000000000000016","图像与多媒体","SCENARIO","使用场景",{"categoryNo":103,"categoryName":104,"taxonomyType":100,"taxonomyTypeDesc":101},"01JBSEEDCAT000000000000023","Skill与智能体开发",[106,107,108,109,110,111,112],"ltx","workflow ltx i2v","workflow","Production","task","when","use",{"skillNo":114,"name":115,"nameZh":6,"description":116,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":117,"categories":118,"keywords":121,"publishedAt":43},"01M71J5NYW853AAPB17VR0F7ZX","workflow-flux2-text-to-image","Flux2 Text-to-Image Workflow Reference.","https:\u002F\u002Fgithub.com\u002F0xzgbot\u002Fforge-nps-v01\u002Fblob\u002Fmaster\u002Fhermes_home\u002Fskills\u002Fworkflow_flux2_text_to_image\u002FSKILL.md",[119,120],{"categoryNo":98,"categoryName":99,"taxonomyType":100,"taxonomyTypeDesc":101},{"categoryNo":103,"categoryName":104,"taxonomyType":100,"taxonomyTypeDesc":101},[122,123,108,124,125,109,126],"workflow flux2 text to image","image","text","reference","Text-to-Image",{"skillNo":128,"name":129,"nameZh":6,"description":130,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":131,"categories":132,"keywords":134,"publishedAt":43},"01M71J5KWY96MD6B6YC2XP6NXB","wide-angle-environmental","Use when the task involves wide angle environmental. DESCRIPTION.","https:\u002F\u002Fgithub.com\u002F0xzgbot\u002Fforge-nps-v01\u002Fblob\u002Fmaster\u002Fhermes_home\u002Fskills\u002Fwide_angle_environmental\u002FSKILL.md",[133],{"categoryNo":103,"categoryName":104,"taxonomyType":100,"taxonomyTypeDesc":101},[135,136,137,109,110,111,112],"environmental","wide","wide angle environmental",{"skillNo":139,"name":140,"nameZh":6,"description":141,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":142,"categories":143,"keywords":145,"publishedAt":43},"01M71J5DXHECTDVDF8HRP81YFY","wes-anderson-specialist","Use when the task involves wes anderson specialist. 1. EXECUTIVE SUMMARY.","https:\u002F\u002Fgithub.com\u002F0xzgbot\u002Fforge-nps-v01\u002Fblob\u002Fmaster\u002Fhermes_home\u002Fskills\u002Fwes_anderson_specialist\u002FSKILL.md",[144],{"categoryNo":103,"categoryName":104,"taxonomyType":100,"taxonomyTypeDesc":101},[146,147,148,149,111,112,110,109],"specialist","wes anderson specialist","summary","executive summary",{"skillNo":151,"name":152,"nameZh":6,"description":153,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":154,"categories":155,"keywords":157,"publishedAt":43},"01M71J5BRHX2N4BRG2VQMWJK3E","volumetric-god-rays-lighting","Use when the task involves volumetric god rays lighting. DESCRIPTION.","https:\u002F\u002Fgithub.com\u002F0xzgbot\u002Fforge-nps-v01\u002Fblob\u002Fmaster\u002Fhermes_home\u002Fskills\u002Fvolumetric_god_rays_lighting\u002FSKILL.md",[156],{"categoryNo":103,"categoryName":104,"taxonomyType":100,"taxonomyTypeDesc":101},[158,159,112,111,110,109],"volumetric god rays lighting","lighting"]