[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M6DFSVBXKWAQS0Y9M70MPVMY":3,"skill-same:01M6DFSVBXKWAQS0Y9M70MPVMY":76},{"skillNo":4,"name":5,"nameZh":6,"description":7,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":16,"categories":17,"keywords":23,"publishedAt":35,"linkOnly":36,"markdownContent":37,"markdownBody":38,"frontMatter":39,"refName":54,"commitSha":55,"skillDirectory":56,"forksCount":57,"repositoryHtmlUrl":58,"repositoryHomepageUrl":6,"ownerHtmlUrl":59,"ownerBlogUrl":6,"ownerDescription":60,"updatedAt":61,"install":62,"resources":75},"01M6DFSVBXKWAQS0Y9M70MPVMY","python-debugpy",null,"Debug Python: pdb REPL + debugpy remote (DAP).","zapabob\u002FSkills","zapabob","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F130830376?v=4","01M6DBMYZAGRTFQDSQYW3W764P","峯岸 亮","01M6DBMZ3GKDSJ01NZ9XTZD7JD",2,"MIT","https:\u002F\u002Fgithub.com\u002Fzapabob\u002FSkills\u002Fblob\u002Fmain\u002Farchive\u002Fskill-md-reviewed\u002Fsha256\u002F52\u002F52aa8812a36eb7e8e2afe883c8f7de81c5a96f53aee3679b97e50be38041652a\u002FSKILL.md",[18],{"categoryNo":19,"categoryName":20,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000030","开发工程师","PROFESSION","适用职业",[24,25,26,27,28,29,30,31,32,33,34],"python","python debugpy","macos","linux","debug","debugging","debugger","hermes","hermes agent","node","systematic","2026-11-10 10:12:30",false,"---\r\nname: python-debugpy\r\ndescription: \"Debug Python: pdb REPL + debugpy remote (DAP).\"\r\nversion: 1.0.0\r\nauthor: Hermes Agent\r\nlicense: MIT\r\nplatforms: [linux, macos]\r\nmetadata:\r\n  hermes:\r\n    tags: [debugging, python, pdb, debugpy, breakpoints, dap, post-mortem]\r\n    related_skills: [systematic-debugging, node-inspect-debugger]\r\n---\r\n\r\n# Python Debugger (pdb + debugpy)\r\n\r\n## Overview\r\n\r\nThree tools, picked by situation:\r\n\r\n| Tool | When |\r\n|---|---|\r\n| **`breakpoint()` + pdb** | Local, interactive, simplest. Add `breakpoint()` in the source, run normally, get a REPL at that line. |\r\n| **`python -m pdb`** | Launch an existing script under pdb with no source edits. Useful for quick poking. |\r\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). |\r\n\r\n**Start with `breakpoint()`.** It's the cheapest thing that works.\r\n\r\n## When to Use\r\n\r\n- A test fails and the traceback doesn't reveal why a value is wrong\r\n- You need to step through a function and watch a collection mutate\r\n- A long-running process (hermes gateway, tui_gateway) misbehaves and you can't restart it\r\n- Post-mortem: an exception fired in prod-ish code and you want to inspect locals at the crash site\r\n- A subprocess \u002F child (Python `_SlashWorker`, PTY bridge worker) is the actual bug site\r\n\r\n**Don't use for:** things `print()` \u002F `logging.debug` solve in under a minute, or things `pytest -vv --tb=long --showlocals` already reveals.\r\n\r\n## pdb Quick Reference\r\n\r\nInside any pdb prompt (`(Pdb)`):\r\n\r\n| Command | Action |\r\n|---|---|\r\n| `h` \u002F `h cmd` | help |\r\n| `n` | next line (step over) |\r\n| `s` | step into |\r\n| `r` | return from current function |\r\n| `c` | continue |\r\n| `unt N` | continue until line N |\r\n| `j N` | jump to line N (same function only) |\r\n| `l` \u002F `ll` | list source around current line \u002F full function |\r\n| `w` | where (stack trace) |\r\n| `u` \u002F `d` | move up \u002F down in the stack |\r\n| `a` | print args of the current function |\r\n| `p expr` \u002F `pp expr` | print \u002F pretty-print expression |\r\n| `display expr` | auto-print expr on every stop |\r\n| `b file:line` | set breakpoint |\r\n| `b func` | break on function entry |\r\n| `b file:line, cond` | conditional breakpoint |\r\n| `cl N` | clear breakpoint N |\r\n| `tbreak file:line` | one-shot breakpoint |\r\n| `!stmt` | execute arbitrary Python (assignments included) |\r\n| `interact` | drop into full Python REPL in current scope (Ctrl+D to exit) |\r\n| `q` | quit |\r\n\r\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.\r\n\r\n## Recipe 1: Local breakpoint\r\n\r\nEasiest. Edit the file:\r\n\r\n```python\r\ndef compute(x, y):\r\n    result = some_helper(x)\r\n    breakpoint()           # \u003C-- drops into pdb here\r\n    return result + y\r\n```\r\n\r\nRun the code normally. You land at the `breakpoint()` line with full access to locals.\r\n\r\n**Don't forget to remove `breakpoint()` before committing.** Use `git diff` or a pre-commit grep:\r\n```bash\r\nrg -n 'breakpoint\\(\\)' --type py\r\n```\r\n\r\n## Recipe 2: Launch a script under pdb (no source edits)\r\n\r\n```bash\r\npython -m pdb path\u002Fto\u002Fscript.py arg1 arg2\r\n# Lands at first line of script\r\n(Pdb) b path\u002Fto\u002Fscript.py:42\r\n(Pdb) c\r\n```\r\n\r\n## Recipe 3: Debug a pytest test\r\n\r\nThe hermes test runner and pytest both support this:\r\n\r\n```bash\r\n# Drop to pdb on failure (or on any raised exception):\r\nscripts\u002Frun_tests.sh tests\u002Fpath\u002Fto\u002Ftest_file.py::test_name --pdb\r\n\r\n# Drop to pdb at the START of the test:\r\nscripts\u002Frun_tests.sh tests\u002Fpath\u002Fto\u002Ftest_file.py::test_name --trace\r\n\r\n# Show locals in tracebacks without pdb:\r\nscripts\u002Frun_tests.sh tests\u002Fpath\u002Fto\u002Ftest_file.py --showlocals --tb=long\r\n```\r\n\r\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`:\r\n\r\n```bash\r\nscripts\u002Frun_tests.sh tests\u002Ffoo_test.py::test_bar --pdb -p no:xdist\r\n# or\r\nsource .venv\u002Fbin\u002Factivate\r\npython -m pytest tests\u002Ffoo_test.py::test_bar --pdb\r\n```\r\n\r\nThis bypasses the hermetic-env guarantees — fine for debugging, but re-run under the wrapper to confirm before pushing.\r\n\r\n## Recipe 4: Post-mortem on any exception\r\n\r\n```python\r\nimport pdb, sys\r\ntry:\r\n    run_the_thing()\r\nexcept Exception:\r\n    pdb.post_mortem(sys.exc_info()[2])\r\n```\r\n\r\nOr wrap a whole script:\r\n\r\n```bash\r\npython -m pdb -c continue script.py\r\n# When it crashes, pdb catches it and you're in the frame of the exception\r\n```\r\n\r\nOr set a global hook in a repl\u002Fjupyter:\r\n\r\n```python\r\nimport sys\r\ndef excepthook(etype, value, tb):\r\n    import pdb; pdb.post_mortem(tb)\r\nsys.excepthook = excepthook\r\n```\r\n\r\n## Recipe 5: Remote debug with debugpy (attach to running process)\r\n\r\nFor long-lived processes: Hermes gateway, tui_gateway, a daemon, a process that's already misbehaving and can't be restarted clean.\r\n\r\n### Setup\r\n\r\n```bash\r\nsource \u002Fhome\u002Fbb\u002Fhermes-agent\u002F.venv\u002Fbin\u002Factivate\r\npip install debugpy\r\n```\r\n\r\n### Pattern A: Source-edit — process waits for debugger at launch\r\n\r\nAdd near the top of the entry point (or inside the function you want to debug):\r\n\r\n```python\r\nimport debugpy\r\ndebugpy.listen((\"127.0.0.1\", 5678))\r\nprint(\"debugpy listening on 5678, waiting for client...\", flush=True)\r\ndebugpy.wait_for_client()\r\ndebugpy.breakpoint()       # optional: pause immediately once attached\r\n```\r\n\r\nStart the process; it blocks on `wait_for_client()`.\r\n\r\n### Pattern B: No source edit — launch with `-m debugpy`\r\n\r\n```bash\r\npython -m debugpy --listen 127.0.0.1:5678 --wait-for-client your_script.py arg1\r\n```\r\n\r\nEquivalent for module entry:\r\n\r\n```bash\r\npython -m debugpy --listen 127.0.0.1:5678 --wait-for-client -m your.module\r\n```\r\n\r\n### Pattern C: Attach to an already-running process\r\n\r\nNeeds the PID and debugpy preinstalled in the target's environment:\r\n\r\n```bash\r\npython -m debugpy --listen 127.0.0.1:5678 --pid \u003Cpid>\r\n# debugpy injects itself into the process. Then attach a client as below.\r\n```\r\n\r\nSome kernels\u002Fsecurity configs block the ptrace-based injection (`\u002Fproc\u002Fsys\u002Fkernel\u002Fyama\u002Fptrace_scope`). Fix with:\r\n```bash\r\necho 0 | sudo tee \u002Fproc\u002Fsys\u002Fkernel\u002Fyama\u002Fptrace_scope\r\n```\r\n\r\n### Connecting a client from the terminal\r\n\r\nThe easiest terminal-side DAP client is VS Code CLI or a small script. From inside Hermes you have two practical options:\r\n\r\n**Option 1: `debugpy`'s own CLI REPL** — not an official feature, but a tiny DAP client script:\r\n\r\n```python\r\n# \u002Ftmp\u002Fdap_client.py\r\nimport socket, json, itertools, time, sys\r\n\r\nHOST, PORT = \"127.0.0.1\", 5678\r\ns = socket.create_connection((HOST, PORT))\r\nseq = itertools.count(1)\r\n\r\ndef send(msg):\r\n    msg[\"seq\"] = next(seq)\r\n    body = json.dumps(msg).encode()\r\n    s.sendall(f\"Content-Length: {len(body)}\\r\\n\\r\\n\".encode() + body)\r\n\r\ndef recv():\r\n    header = b\"\"\r\n    while b\"\\r\\n\\r\\n\" not in header:\r\n        header += s.recv(1)\r\n    length = int(header.decode().split(\"Content-Length:\")[1].split(\"\\r\\n\")[0].strip())\r\n    body = b\"\"\r\n    while len(body) \u003C length:\r\n        body += s.recv(length - len(body))\r\n    return json.loads(body)\r\n\r\nsend({\"type\": \"request\", \"command\": \"initialize\", \"arguments\": {\"adapterID\": \"python\"}})\r\nprint(recv())\r\nsend({\"type\": \"request\", \"command\": \"attach\", \"arguments\": {}})\r\nprint(recv())\r\nsend({\"type\": \"request\", \"command\": \"setBreakpoints\",\r\n      \"arguments\": {\"source\": {\"path\": sys.argv[1]},\r\n                    \"breakpoints\": [{\"line\": int(sys.argv[2])}]}})\r\nprint(recv())\r\nsend({\"type\": \"request\", \"command\": \"configurationDone\"})\r\n# ... loop reading events and sending continue\u002FstepIn\u002Fetc.\r\n```\r\n\r\nThis is fine for one-off automation but painful as an interactive UX.\r\n\r\n**Option 2: Attach from VS Code \u002F Cursor \u002F Zed** — if the user has one open, they can add a `launch.json`:\r\n\r\n```json\r\n{\r\n  \"name\": \"Attach to Hermes\",\r\n  \"type\": \"debugpy\",\r\n  \"request\": \"attach\",\r\n  \"connect\": { \"host\": \"127.0.0.1\", \"port\": 5678 },\r\n  \"justMyCode\": false,\r\n  \"pathMappings\": [\r\n    { \"localRoot\": \"${workspaceFolder}\", \"remoteRoot\": \"\u002Fhome\u002Fbb\u002Fhermes-agent\" }\r\n  ]\r\n}\r\n```\r\n\r\n**Option 3: Ditch DAP, use `remote-pdb`** — usually what you actually want from a terminal agent:\r\n\r\n```bash\r\npip install remote-pdb\r\n```\r\n\r\nIn your code:\r\n```python\r\nfrom remote_pdb import set_trace\r\nset_trace(host=\"127.0.0.1\", port=4444)   # blocks until connection\r\n```\r\n\r\nThen from the terminal:\r\n```bash\r\nnc 127.0.0.1 4444\r\n# You get a (Pdb) prompt exactly as if debugging locally.\r\n```\r\n\r\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.\r\n\r\n## Debugging Hermes-specific Processes\r\n\r\n### Tests\r\nSee Recipe 3. Always add `-p no:xdist` or run single tests without xdist.\r\n\r\n### `run_agent.py` \u002F CLI — one-shot\r\nEasiest: add `breakpoint()` near the suspect line, then run `hermes` normally. Control returns to your terminal at the pause point.\r\n\r\n### `tui_gateway` subprocess (spawned by `hermes --tui`)\r\nThe gateway runs as a child of the Node TUI. Options:\r\n\r\n**A. Source-edit the gateway:**\r\n```python\r\n# tui_gateway\u002Fserver.py near the top of serve()\r\nimport debugpy\r\ndebugpy.listen((\"127.0.0.1\", 5678))\r\ndebugpy.wait_for_client()\r\n```\r\nStart `hermes --tui`. The TUI will appear frozen (its backend is waiting). Attach a client; execution resumes when you `continue`.\r\n\r\n**B. Use `remote-pdb` at a specific handler:**\r\n```python\r\nfrom remote_pdb import set_trace\r\nset_trace(host=\"127.0.0.1\", port=4444)   # in the RPC handler you want to trap\r\n```\r\nTrigger the matching slash command from the TUI, then `nc 127.0.0.1 4444` in another terminal.\r\n\r\n### `_SlashWorker` subprocess\r\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.\r\n\r\n### Gateway (`gateway\u002Frun.py`)\r\nLong-lived. Use `remote-pdb` at a handler, or `debugpy` with `--wait-for-client` if you're restarting the gateway anyway.\r\n\r\n## Common Pitfalls\r\n\r\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`.\r\n\r\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.\r\n\r\n3. **`PYTHONBREAKPOINT=0`** disables all `breakpoint()` calls. Check the env if your breakpoint isn't hitting:\r\n   ```bash\r\n   echo $PYTHONBREAKPOINT\r\n   ```\r\n\r\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.\r\n\r\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.\r\n\r\n6. **Threads.** `pdb` only debugs the current thread. For multithreaded code, use `debugpy` (thread-aware DAP) or set `threading.settrace()` per thread.\r\n\r\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`.\r\n\r\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.\r\n\r\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.\r\n\r\n## Verification Checklist\r\n\r\n- [ ] After `pip install debugpy`, confirm: `python -c \"import debugpy; print(debugpy.__version__)\"`\r\n- [ ] For remote debug, confirm the port is actually listening: `ss -tlnp | grep 5678`\r\n- [ ] First breakpoint actually hits (if it doesn't, you likely have `PYTHONBREAKPOINT=0`, you're under xdist, or execution finished before attach)\r\n- [ ] `where` \u002F `w` shows the expected call stack\r\n- [ ] Post-debug cleanup: no stray `breakpoint()` \u002F `set_trace()` in committed code\r\n  ```bash\r\n  rg -n 'breakpoint\\(\\)|set_trace\\(|debugpy\\.listen' --type py\r\n  ```\r\n\r\n## One-Shot Recipes\r\n\r\n**\"Why is this dict missing a key?\"**\r\n```python\r\n# add above the KeyError site\r\nbreakpoint()\r\n# then in pdb:\r\n(Pdb) pp d\r\n(Pdb) pp list(d.keys())\r\n(Pdb) w                # how did we get here\r\n```\r\n\r\n**\"This test passes in isolation but fails in the suite.\"**\r\n```bash\r\nscripts\u002Frun_tests.sh tests\u002Fthe_test.py --pdb -p no:xdist\r\n# But if it only fails WITH other tests:\r\nsource .venv\u002Fbin\u002Factivate\r\npython -m pytest tests\u002F -x --pdb -p no:xdist\r\n# Now it pdb-traps at the exact failing test after state accumulated.\r\n```\r\n\r\n**\"My async handler deadlocks.\"**\r\n```python\r\n# Add at handler entry\r\nimport remote_pdb; remote_pdb.set_trace(host=\"127.0.0.1\", port=4444)\r\n```\r\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.\r\n\r\n**\"Post-mortem on a crash in an Ink child process \u002F subprocess.\"**\r\n```bash\r\nPYTHONFAULTHANDLER=1 python -m pdb -c continue path\u002Fto\u002Fentrypoint.py\r\n# On crash, pdb lands at the frame of the exception with full locals\r\n```\r\n","# Python Debugger (pdb + debugpy)\r\n\r\n## Overview\r\n\r\nThree tools, picked by situation:\r\n\r\n| Tool | When |\r\n|---|---|\r\n| **`breakpoint()` + pdb** | Local, interactive, simplest. Add `breakpoint()` in the source, run normally, get a REPL at that line. |\r\n| **`python -m pdb`** | Launch an existing script under pdb with no source edits. Useful for quick poking. |\r\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). |\r\n\r\n**Start with `breakpoint()`.** It's the cheapest thing that works.\r\n\r\n## When to Use\r\n\r\n- A test fails and the traceback doesn't reveal why a value is wrong\r\n- You need to step through a function and watch a collection mutate\r\n- A long-running process (hermes gateway, tui_gateway) misbehaves and you can't restart it\r\n- Post-mortem: an exception fired in prod-ish code and you want to inspect locals at the crash site\r\n- A subprocess \u002F child (Python `_SlashWorker`, PTY bridge worker) is the actual bug site\r\n\r\n**Don't use for:** things `print()` \u002F `logging.debug` solve in under a minute, or things `pytest -vv --tb=long --showlocals` already reveals.\r\n\r\n## pdb Quick Reference\r\n\r\nInside any pdb prompt (`(Pdb)`):\r\n\r\n| Command | Action |\r\n|---|---|\r\n| `h` \u002F `h cmd` | help |\r\n| `n` | next line (step over) |\r\n| `s` | step into |\r\n| `r` | return from current function |\r\n| `c` | continue |\r\n| `unt N` | continue until line N |\r\n| `j N` | jump to line N (same function only) |\r\n| `l` \u002F `ll` | list source around current line \u002F full function |\r\n| `w` | where (stack trace) |\r\n| `u` \u002F `d` | move up \u002F down in the stack |\r\n| `a` | print args of the current function |\r\n| `p expr` \u002F `pp expr` | print \u002F pretty-print expression |\r\n| `display expr` | auto-print expr on every stop |\r\n| `b file:line` | set breakpoint |\r\n| `b func` | break on function entry |\r\n| `b file:line, cond` | conditional breakpoint |\r\n| `cl N` | clear breakpoint N |\r\n| `tbreak file:line` | one-shot breakpoint |\r\n| `!stmt` | execute arbitrary Python (assignments included) |\r\n| `interact` | drop into full Python REPL in current scope (Ctrl+D to exit) |\r\n| `q` | quit |\r\n\r\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.\r\n\r\n## Recipe 1: Local breakpoint\r\n\r\nEasiest. Edit the file:\r\n\r\n```python\r\ndef compute(x, y):\r\n    result = some_helper(x)\r\n    breakpoint()           # \u003C-- drops into pdb here\r\n    return result + y\r\n```\r\n\r\nRun the code normally. You land at the `breakpoint()` line with full access to locals.\r\n\r\n**Don't forget to remove `breakpoint()` before committing.** Use `git diff` or a pre-commit grep:\r\n```bash\r\nrg -n 'breakpoint\\(\\)' --type py\r\n```\r\n\r\n## Recipe 2: Launch a script under pdb (no source edits)\r\n\r\n```bash\r\npython -m pdb path\u002Fto\u002Fscript.py arg1 arg2\r\n# Lands at first line of script\r\n(Pdb) b path\u002Fto\u002Fscript.py:42\r\n(Pdb) c\r\n```\r\n\r\n## Recipe 3: Debug a pytest test\r\n\r\nThe hermes test runner and pytest both support this:\r\n\r\n```bash\r\n# Drop to pdb on failure (or on any raised exception):\r\nscripts\u002Frun_tests.sh tests\u002Fpath\u002Fto\u002Ftest_file.py::test_name --pdb\r\n\r\n# Drop to pdb at the START of the test:\r\nscripts\u002Frun_tests.sh tests\u002Fpath\u002Fto\u002Ftest_file.py::test_name --trace\r\n\r\n# Show locals in tracebacks without pdb:\r\nscripts\u002Frun_tests.sh tests\u002Fpath\u002Fto\u002Ftest_file.py --showlocals --tb=long\r\n```\r\n\r\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`:\r\n\r\n```bash\r\nscripts\u002Frun_tests.sh tests\u002Ffoo_test.py::test_bar --pdb -p no:xdist\r\n# or\r\nsource .venv\u002Fbin\u002Factivate\r\npython -m pytest tests\u002Ffoo_test.py::test_bar --pdb\r\n```\r\n\r\nThis bypasses the hermetic-env guarantees — fine for debugging, but re-run under the wrapper to confirm before pushing.\r\n\r\n## Recipe 4: Post-mortem on any exception\r\n\r\n```python\r\nimport pdb, sys\r\ntry:\r\n    run_the_thing()\r\nexcept Exception:\r\n    pdb.post_mortem(sys.exc_info()[2])\r\n```\r\n\r\nOr wrap a whole script:\r\n\r\n```bash\r\npython -m pdb -c continue script.py\r\n# When it crashes, pdb catches it and you're in the frame of the exception\r\n```\r\n\r\nOr set a global hook in a repl\u002Fjupyter:\r\n\r\n```python\r\nimport sys\r\ndef excepthook(etype, value, tb):\r\n    import pdb; pdb.post_mortem(tb)\r\nsys.excepthook = excepthook\r\n```\r\n\r\n## Recipe 5: Remote debug with debugpy (attach to running process)\r\n\r\nFor long-lived processes: Hermes gateway, tui_gateway, a daemon, a process that's already misbehaving and can't be restarted clean.\r\n\r\n### Setup\r\n\r\n```bash\r\nsource \u002Fhome\u002Fbb\u002Fhermes-agent\u002F.venv\u002Fbin\u002Factivate\r\npip install debugpy\r\n```\r\n\r\n### Pattern A: Source-edit — process waits for debugger at launch\r\n\r\nAdd near the top of the entry point (or inside the function you want to debug):\r\n\r\n```python\r\nimport debugpy\r\ndebugpy.listen((\"127.0.0.1\", 5678))\r\nprint(\"debugpy listening on 5678, waiting for client...\", flush=True)\r\ndebugpy.wait_for_client()\r\ndebugpy.breakpoint()       # optional: pause immediately once attached\r\n```\r\n\r\nStart the process; it blocks on `wait_for_client()`.\r\n\r\n### Pattern B: No source edit — launch with `-m debugpy`\r\n\r\n```bash\r\npython -m debugpy --listen 127.0.0.1:5678 --wait-for-client your_script.py arg1\r\n```\r\n\r\nEquivalent for module entry:\r\n\r\n```bash\r\npython -m debugpy --listen 127.0.0.1:5678 --wait-for-client -m your.module\r\n```\r\n\r\n### Pattern C: Attach to an already-running process\r\n\r\nNeeds the PID and debugpy preinstalled in the target's environment:\r\n\r\n```bash\r\npython -m debugpy --listen 127.0.0.1:5678 --pid \u003Cpid>\r\n# debugpy injects itself into the process. Then attach a client as below.\r\n```\r\n\r\nSome kernels\u002Fsecurity configs block the ptrace-based injection (`\u002Fproc\u002Fsys\u002Fkernel\u002Fyama\u002Fptrace_scope`). Fix with:\r\n```bash\r\necho 0 | sudo tee \u002Fproc\u002Fsys\u002Fkernel\u002Fyama\u002Fptrace_scope\r\n```\r\n\r\n### Connecting a client from the terminal\r\n\r\nThe easiest terminal-side DAP client is VS Code CLI or a small script. From inside Hermes you have two practical options:\r\n\r\n**Option 1: `debugpy`'s own CLI REPL** — not an official feature, but a tiny DAP client script:\r\n\r\n```python\r\n# \u002Ftmp\u002Fdap_client.py\r\nimport socket, json, itertools, time, sys\r\n\r\nHOST, PORT = \"127.0.0.1\", 5678\r\ns = socket.create_connection((HOST, PORT))\r\nseq = itertools.count(1)\r\n\r\ndef send(msg):\r\n    msg[\"seq\"] = next(seq)\r\n    body = json.dumps(msg).encode()\r\n    s.sendall(f\"Content-Length: {len(body)}\\r\\n\\r\\n\".encode() + body)\r\n\r\ndef recv():\r\n    header = b\"\"\r\n    while b\"\\r\\n\\r\\n\" not in header:\r\n        header += s.recv(1)\r\n    length = int(header.decode().split(\"Content-Length:\")[1].split(\"\\r\\n\")[0].strip())\r\n    body = b\"\"\r\n    while len(body) \u003C length:\r\n        body += s.recv(length - len(body))\r\n    return json.loads(body)\r\n\r\nsend({\"type\": \"request\", \"command\": \"initialize\", \"arguments\": {\"adapterID\": \"python\"}})\r\nprint(recv())\r\nsend({\"type\": \"request\", \"command\": \"attach\", \"arguments\": {}})\r\nprint(recv())\r\nsend({\"type\": \"request\", \"command\": \"setBreakpoints\",\r\n      \"arguments\": {\"source\": {\"path\": sys.argv[1]},\r\n                    \"breakpoints\": [{\"line\": int(sys.argv[2])}]}})\r\nprint(recv())\r\nsend({\"type\": \"request\", \"command\": \"configurationDone\"})\r\n# ... loop reading events and sending continue\u002FstepIn\u002Fetc.\r\n```\r\n\r\nThis is fine for one-off automation but painful as an interactive UX.\r\n\r\n**Option 2: Attach from VS Code \u002F Cursor \u002F Zed** — if the user has one open, they can add a `launch.json`:\r\n\r\n```json\r\n{\r\n  \"name\": \"Attach to Hermes\",\r\n  \"type\": \"debugpy\",\r\n  \"request\": \"attach\",\r\n  \"connect\": { \"host\": \"127.0.0.1\", \"port\": 5678 },\r\n  \"justMyCode\": false,\r\n  \"pathMappings\": [\r\n    { \"localRoot\": \"${workspaceFolder}\", \"remoteRoot\": \"\u002Fhome\u002Fbb\u002Fhermes-agent\" }\r\n  ]\r\n}\r\n```\r\n\r\n**Option 3: Ditch DAP, use `remote-pdb`** — usually what you actually want from a terminal agent:\r\n\r\n```bash\r\npip install remote-pdb\r\n```\r\n\r\nIn your code:\r\n```python\r\nfrom remote_pdb import set_trace\r\nset_trace(host=\"127.0.0.1\", port=4444)   # blocks until connection\r\n```\r\n\r\nThen from the terminal:\r\n```bash\r\nnc 127.0.0.1 4444\r\n# You get a (Pdb) prompt exactly as if debugging locally.\r\n```\r\n\r\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.\r\n\r\n## Debugging Hermes-specific Processes\r\n\r\n### Tests\r\nSee Recipe 3. Always add `-p no:xdist` or run single tests without xdist.\r\n\r\n### `run_agent.py` \u002F CLI — one-shot\r\nEasiest: add `breakpoint()` near the suspect line, then run `hermes` normally. Control returns to your terminal at the pause point.\r\n\r\n### `tui_gateway` subprocess (spawned by `hermes --tui`)\r\nThe gateway runs as a child of the Node TUI. Options:\r\n\r\n**A. Source-edit the gateway:**\r\n```python\r\n# tui_gateway\u002Fserver.py near the top of serve()\r\nimport debugpy\r\ndebugpy.listen((\"127.0.0.1\", 5678))\r\ndebugpy.wait_for_client()\r\n```\r\nStart `hermes --tui`. The TUI will appear frozen (its backend is waiting). Attach a client; execution resumes when you `continue`.\r\n\r\n**B. Use `remote-pdb` at a specific handler:**\r\n```python\r\nfrom remote_pdb import set_trace\r\nset_trace(host=\"127.0.0.1\", port=4444)   # in the RPC handler you want to trap\r\n```\r\nTrigger the matching slash command from the TUI, then `nc 127.0.0.1 4444` in another terminal.\r\n\r\n### `_SlashWorker` subprocess\r\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.\r\n\r\n### Gateway (`gateway\u002Frun.py`)\r\nLong-lived. Use `remote-pdb` at a handler, or `debugpy` with `--wait-for-client` if you're restarting the gateway anyway.\r\n\r\n## Common Pitfalls\r\n\r\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`.\r\n\r\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.\r\n\r\n3. **`PYTHONBREAKPOINT=0`** disables all `breakpoint()` calls. Check the env if your breakpoint isn't hitting:\r\n   ```bash\r\n   echo $PYTHONBREAKPOINT\r\n   ```\r\n\r\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.\r\n\r\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.\r\n\r\n6. **Threads.** `pdb` only debugs the current thread. For multithreaded code, use `debugpy` (thread-aware DAP) or set `threading.settrace()` per thread.\r\n\r\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`.\r\n\r\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.\r\n\r\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.\r\n\r\n## Verification Checklist\r\n\r\n- [ ] After `pip install debugpy`, confirm: `python -c \"import debugpy; print(debugpy.__version__)\"`\r\n- [ ] For remote debug, confirm the port is actually listening: `ss -tlnp | grep 5678`\r\n- [ ] First breakpoint actually hits (if it doesn't, you likely have `PYTHONBREAKPOINT=0`, you're under xdist, or execution finished before attach)\r\n- [ ] `where` \u002F `w` shows the expected call stack\r\n- [ ] Post-debug cleanup: no stray `breakpoint()` \u002F `set_trace()` in committed code\r\n  ```bash\r\n  rg -n 'breakpoint\\(\\)|set_trace\\(|debugpy\\.listen' --type py\r\n  ```\r\n\r\n## One-Shot Recipes\r\n\r\n**\"Why is this dict missing a key?\"**\r\n```python\r\n# add above the KeyError site\r\nbreakpoint()\r\n# then in pdb:\r\n(Pdb) pp d\r\n(Pdb) pp list(d.keys())\r\n(Pdb) w                # how did we get here\r\n```\r\n\r\n**\"This test passes in isolation but fails in the suite.\"**\r\n```bash\r\nscripts\u002Frun_tests.sh tests\u002Fthe_test.py --pdb -p no:xdist\r\n# But if it only fails WITH other tests:\r\nsource .venv\u002Fbin\u002Factivate\r\npython -m pytest tests\u002F -x --pdb -p no:xdist\r\n# Now it pdb-traps at the exact failing test after state accumulated.\r\n```\r\n\r\n**\"My async handler deadlocks.\"**\r\n```python\r\n# Add at handler entry\r\nimport remote_pdb; remote_pdb.set_trace(host=\"127.0.0.1\", port=4444)\r\n```\r\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.\r\n\r\n**\"Post-mortem on a crash in an Ink child process \u002F subprocess.\"**\r\n```bash\r\nPYTHONFAULTHANDLER=1 python -m pdb -c continue path\u002Fto\u002Fentrypoint.py\r\n# On crash, pdb lands at the frame of the exception with full locals\r\n```\r\n",{"name":5,"author":40,"license":15,"version":41,"metadata":42,"platforms":53,"description":7},"Hermes Agent","1.0.0",{"hermes":43},{"tags":44,"related_skills":50},[29,24,45,46,47,48,49],"pdb","debugpy","breakpoints","dap","post-mortem",[51,52],"systematic-debugging","node-inspect-debugger",[27,26],"main","0d1434f89f4bc6b07e9d272accd8c1013768820f","archive\u002Fskill-md-reviewed\u002Fsha256\u002F52\u002F52aa8812a36eb7e8e2afe883c8f7de81c5a96f53aee3679b97e50be38041652a",0,"https:\u002F\u002Fgithub.com\u002Fzapabob\u002FSkills","https:\u002F\u002Fgithub.com\u002Fzapabob","The Open University of JAPAN　2019-now\r\n\r\nTokyo University of Science　2007-2010","2026-10-26 16:29:01",{"source":8,"skillName":5,"commands":63,"prompt":73,"zipAvailable":74},[64,67,70],{"tool":65,"command":66},"npx","npx skills add zapabob\u002FSkills --skill python-debugpy",{"tool":68,"command":69},"bunx","bunx skills add zapabob\u002FSkills --skill python-debugpy",{"tool":71,"command":72},"pnpm","pnpm dlx skills add zapabob\u002FSkills --skill python-debugpy","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002Fzapabob\u002FSkills\u002Fblob\u002Fmain\u002Farchive\u002Fskill-md-reviewed\u002Fsha256\u002F52\u002F52aa8812a36eb7e8e2afe883c8f7de81c5a96f53aee3679b97e50be38041652a\u002FSKILL.md",true,[],[77,107,142,169,196,213],{"skillNo":78,"name":79,"nameZh":6,"description":80,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":81,"categories":82,"keywords":92,"publishedAt":35},"01M6GJTE1GESGGF9K028Y7753E","xurl","X\u002FTwitter via xurl CLI: post, search, DM, media, v2 API.","https:\u002F\u002Fgithub.com\u002Fzapabob\u002FSkills\u002Fblob\u002Fmain\u002Fregistry\u002Fskills\u002Fxurl\u002Fvariants\u002Fhermes-agent\u002FSKILL.md",[83,88,91],{"categoryNo":84,"categoryName":85,"taxonomyType":86,"taxonomyTypeDesc":87},"01JBSEEDCAT000000000000010","后端与接口","SCENARIO","使用场景",{"categoryNo":89,"categoryName":90,"taxonomyType":86,"taxonomyTypeDesc":87},"01JBSEEDCAT000000000000023","Skill与智能体开发",{"categoryNo":19,"categoryName":20,"taxonomyType":21,"taxonomyTypeDesc":22},[79,93,26,94,95,27,96,97,98,99,100,101,102,31,32,103,54,104,105,106],"openclaw","cli","github","windows","official","search","https","social-media","api","Media","twitter","social","via","post",{"skillNo":108,"name":109,"nameZh":6,"description":110,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":111,"categories":112,"keywords":124,"publishedAt":35},"01M6GJT8S1PXQ8WPXT1HSNBWH2","writing-plans","Write implementation plans: bite-sized tasks, paths, code.","https:\u002F\u002Fgithub.com\u002Fzapabob\u002FSkills\u002Fblob\u002Fmain\u002Fregistry\u002Fskills\u002Fwriting-plans\u002Fvariants\u002Fhermes-agent\u002FSKILL.md",[113,116,119,120,121],{"categoryNo":114,"categoryName":115,"taxonomyType":86,"taxonomyTypeDesc":87},"01JBSEEDCAT000000000000015","视觉设计",{"categoryNo":117,"categoryName":118,"taxonomyType":86,"taxonomyTypeDesc":87},"01JBSEEDCAT000000000000018","写作与文案",{"categoryNo":89,"categoryName":90,"taxonomyType":86,"taxonomyTypeDesc":87},{"categoryNo":19,"categoryName":20,"taxonomyType":21,"taxonomyTypeDesc":22},{"categoryNo":122,"categoryName":123,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000036","内容创作者",[125,126,127,128,26,129,27,130,96,131,132,133,134,135,136,137,138,139,140,141],"writing","writing plans","plans","design","review","workflow","documentation","tasks","development","code","planning","code-review","subagent","write","superpowers","paths","test-driven-development",{"skillNo":143,"name":144,"nameZh":6,"description":145,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":146,"categories":147,"keywords":151,"publishedAt":35},"01M6GJST0H5X7WHBJ3BZK0DVKH","whisper","OpenAI's general-purpose speech recognition model. Supports 99 languages, transcription, translation to English, and language identification. Six model sizes from tiny (39M params) to large (1550M params). Use for speech-to-text, podcast transcription, or multilingual audio processing. Best for robust, multilingual ASR.","https:\u002F\u002Fgithub.com\u002Fzapabob\u002FSkills\u002Fblob\u002Fmain\u002Fregistry\u002Fskills\u002Fwhisper\u002Fvariants\u002Fhermes-agent-optional\u002FSKILL.md",[148],{"categoryNo":149,"categoryName":150,"taxonomyType":86,"taxonomyTypeDesc":87},"01JBSEEDCAT000000000000016","图像与多媒体",[144,26,27,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168],"openai","speech","multimodal","Transformers","ASR","Audio","text","model","speech recognition","use","general","multilingual","translation","english","identification","processing","Audio Processing",{"skillNo":170,"name":171,"nameZh":6,"description":172,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":173,"categories":174,"keywords":178,"publishedAt":35},"01M6GJSMRQ8EETZTYMG72P70EA","weights-and-biases","W&B: log ML experiments, sweeps, model registry, dashboards.","https:\u002F\u002Fgithub.com\u002Fzapabob\u002FSkills\u002Fblob\u002Fmain\u002Fregistry\u002Fskills\u002Fweights-and-biases\u002Fvariants\u002Fhermes-agent\u002FSKILL.md",[175],{"categoryNo":176,"categoryName":177,"taxonomyType":86,"taxonomyTypeDesc":87},"01JBSEEDCAT000000000000013","分析与可视化",[179,26,180,181,96,182,183,184,185,186,187,159,188,189,190,191,192,193,194,195],"weights and biases","tensorflow","experiments","pytorch","visualization","registry","real-time","tuning","dashboards","experiment","log","time","Experiment Tracking","Model Registry","Hyperparameter Tuning","WandB","Real-Time Visualization",{"skillNo":197,"name":198,"nameZh":6,"description":199,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":200,"categories":201,"keywords":203,"publishedAt":35},"01M6GJS19KDPRY112JTR326S06","watchers","Poll RSS, JSON APIs, and GitHub with watermark dedup.","https:\u002F\u002Fgithub.com\u002Fzapabob\u002FSkills\u002Fblob\u002Fmain\u002Fregistry\u002Fskills\u002Fwatchers\u002Fvariants\u002Fhermes-agent-optional\u002FSKILL.md",[202],{"categoryNo":89,"categoryName":90,"taxonomyType":86,"taxonomyTypeDesc":87},[198,26,204,95,27,205,206,207,208,209,210,31,32,211,212],"terminal","automation","http","monitoring","cron","json","rss","devops","with",{"skillNo":214,"name":215,"nameZh":6,"description":216,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":217,"categories":218,"keywords":221,"publishedAt":35},"01M6GJRHJD2A6Q830SWF9W48BC","serving-llms-vllm","vLLM: high-throughput LLM serving, OpenAI API, quantization.","https:\u002F\u002Fgithub.com\u002Fzapabob\u002FSkills\u002Fblob\u002Fmain\u002Fregistry\u002Fskills\u002Fvllm\u002Fvariants\u002Fhermes-agent\u002FSKILL.md",[219,220],{"categoryNo":84,"categoryName":85,"taxonomyType":86,"taxonomyTypeDesc":87},{"categoryNo":19,"categoryName":20,"taxonomyType":21,"taxonomyTypeDesc":22},[222,223,224,27,225,152,226,227,228,155,229,101,230,231,232,233,234,235,236,237],"llms","vLLM","serving llms vllm","research","openai api","tensor","Inference Serving","Production","llm serving","High Throughput","PagedAttention","Continuous Batching","Quantization","Tensor Parallelism","high-throughput","throughput"]