[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M6GF99ZCS7BBN230VT2YYCVG":3,"skill-same:01M6GF99ZCS7BBN230VT2YYCVG":78},{"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":38,"linkOnly":39,"markdownContent":40,"markdownBody":41,"frontMatter":42,"refName":56,"commitSha":57,"skillDirectory":58,"forksCount":59,"repositoryHtmlUrl":60,"repositoryHomepageUrl":6,"ownerHtmlUrl":61,"ownerBlogUrl":6,"ownerDescription":62,"updatedAt":63,"install":64,"resources":77},"01M6GF99ZCS7BBN230VT2YYCVG","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\u002Fd6\u002Fd64377eed7b8aa99008ecd43fc858d8fe73d08bb2a88a15bf82bfbe1d18ccf36\u002FSKILL.md",[18],{"categoryNo":19,"categoryName":20,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000030","开发工程师","PROFESSION","适用职业",[24,25,26,27,28,29,30,31,32,33,34,35,36,37],"python","python debugpy","macos","linux","debug","debugging","debugger","hermes","hermes agent","node","systematic","pdb","post","remote","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\nplatforms: [linux, macos]\nmetadata:\n  hermes:\n    tags: [debugging, python, pdb, debugpy, breakpoints, dap, post-mortem]\n    related_skills: [systematic-debugging, node-inspect-debugger]\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` runs each test file in a captured subprocess via `run_tests_parallel.py` (no xdist), so interactive pdb does NOT work under the wrapper. Run pytest directly for `--pdb`:\n\n```bash\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. The wrapper captures subprocess output, so run pytest directly for interactive pdb.\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 a parallel\u002Foutput-capturing runner silently does nothing.** You won't see the prompt, the test just hangs (true of pytest-xdist and of `scripts\u002Frun_tests.sh`'s captured per-file subprocesses). Run pytest directly on a single file for interactive debugging.\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 a parallel\u002Fcapturing runner, 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   # confirm it fails under the isolated runner first\n# For interactive debugging, or if it only fails WITH other tests:\nsource .venv\u002Fbin\u002Factivate\npython -m pytest tests\u002F -x --pdb\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` runs each test file in a captured subprocess via `run_tests_parallel.py` (no xdist), so interactive pdb does NOT work under the wrapper. Run pytest directly for `--pdb`:\n\n```bash\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. The wrapper captures subprocess output, so run pytest directly for interactive pdb.\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 a parallel\u002Foutput-capturing runner silently does nothing.** You won't see the prompt, the test just hangs (true of pytest-xdist and of `scripts\u002Frun_tests.sh`'s captured per-file subprocesses). Run pytest directly on a single file for interactive debugging.\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 a parallel\u002Fcapturing runner, 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   # confirm it fails under the isolated runner first\n# For interactive debugging, or if it only fails WITH other tests:\nsource .venv\u002Fbin\u002Factivate\npython -m pytest tests\u002F -x --pdb\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":43,"license":15,"version":44,"metadata":45,"platforms":55,"description":7},"Hermes Agent","1.0.0",{"hermes":46},{"tags":47,"related_skills":52},[29,24,35,48,49,50,51],"debugpy","breakpoints","dap","post-mortem",[53,54],"systematic-debugging","node-inspect-debugger",[27,26],"main","0d1434f89f4bc6b07e9d272accd8c1013768820f","archive\u002Fskill-md-reviewed\u002Fsha256\u002Fd6\u002Fd64377eed7b8aa99008ecd43fc858d8fe73d08bb2a88a15bf82bfbe1d18ccf36",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":65,"prompt":75,"zipAvailable":76},[66,69,72],{"tool":67,"command":68},"npx","npx skills add zapabob\u002FSkills --skill python-debugpy",{"tool":70,"command":71},"bunx","bunx skills add zapabob\u002FSkills --skill python-debugpy",{"tool":73,"command":74},"pnpm","pnpm dlx skills add zapabob\u002FSkills --skill python-debugpy","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002Fzapabob\u002FSkills\u002Fblob\u002Fmain\u002Farchive\u002Fskill-md-reviewed\u002Fsha256\u002Fd6\u002Fd64377eed7b8aa99008ecd43fc858d8fe73d08bb2a88a15bf82bfbe1d18ccf36\u002FSKILL.md",true,[],[79,108,143,170,197,214],{"skillNo":80,"name":81,"nameZh":6,"description":82,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":83,"categories":84,"keywords":94,"publishedAt":38},"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",[85,90,93],{"categoryNo":86,"categoryName":87,"taxonomyType":88,"taxonomyTypeDesc":89},"01JBSEEDCAT000000000000010","后端与接口","SCENARIO","使用场景",{"categoryNo":91,"categoryName":92,"taxonomyType":88,"taxonomyTypeDesc":89},"01JBSEEDCAT000000000000023","Skill与智能体开发",{"categoryNo":19,"categoryName":20,"taxonomyType":21,"taxonomyTypeDesc":22},[81,95,26,96,97,27,98,99,100,101,102,103,104,31,32,105,56,106,107,36],"openclaw","cli","github","windows","official","search","https","social-media","api","Media","twitter","social","via",{"skillNo":109,"name":110,"nameZh":6,"description":111,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":112,"categories":113,"keywords":125,"publishedAt":38},"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",[114,117,120,121,122],{"categoryNo":115,"categoryName":116,"taxonomyType":88,"taxonomyTypeDesc":89},"01JBSEEDCAT000000000000015","视觉设计",{"categoryNo":118,"categoryName":119,"taxonomyType":88,"taxonomyTypeDesc":89},"01JBSEEDCAT000000000000018","写作与文案",{"categoryNo":91,"categoryName":92,"taxonomyType":88,"taxonomyTypeDesc":89},{"categoryNo":19,"categoryName":20,"taxonomyType":21,"taxonomyTypeDesc":22},{"categoryNo":123,"categoryName":124,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000036","内容创作者",[126,127,128,129,26,130,27,131,98,132,133,134,135,136,137,138,139,140,141,142],"writing","writing plans","plans","design","review","workflow","documentation","tasks","development","code","planning","code-review","subagent","write","superpowers","paths","test-driven-development",{"skillNo":144,"name":145,"nameZh":6,"description":146,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":147,"categories":148,"keywords":152,"publishedAt":38},"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",[149],{"categoryNo":150,"categoryName":151,"taxonomyType":88,"taxonomyTypeDesc":89},"01JBSEEDCAT000000000000016","图像与多媒体",[145,26,27,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169],"openai","speech","multimodal","Transformers","ASR","Audio","text","model","speech recognition","use","general","multilingual","translation","english","identification","processing","Audio Processing",{"skillNo":171,"name":172,"nameZh":6,"description":173,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":174,"categories":175,"keywords":179,"publishedAt":38},"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",[176],{"categoryNo":177,"categoryName":178,"taxonomyType":88,"taxonomyTypeDesc":89},"01JBSEEDCAT000000000000013","分析与可视化",[180,26,181,182,98,183,184,185,186,187,188,160,189,190,191,192,193,194,195,196],"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":198,"name":199,"nameZh":6,"description":200,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":201,"categories":202,"keywords":204,"publishedAt":38},"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",[203],{"categoryNo":91,"categoryName":92,"taxonomyType":88,"taxonomyTypeDesc":89},[199,26,205,97,27,206,207,208,209,210,211,31,32,212,213],"terminal","automation","http","monitoring","cron","json","rss","devops","with",{"skillNo":215,"name":216,"nameZh":6,"description":217,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":218,"categories":219,"keywords":222,"publishedAt":38},"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",[220,221],{"categoryNo":86,"categoryName":87,"taxonomyType":88,"taxonomyTypeDesc":89},{"categoryNo":19,"categoryName":20,"taxonomyType":21,"taxonomyTypeDesc":22},[223,224,225,27,226,153,227,228,229,156,230,103,231,232,233,234,235,236,237,238],"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"]