[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M71QFM7KWM540YR67FRN807Q":3,"skill-same:01M71QFM7KWM540YR67FRN807Q":76},{"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":13,"repositoryHtmlUrl":59,"repositoryHomepageUrl":6,"ownerHtmlUrl":60,"ownerBlogUrl":6,"ownerDescription":6,"updatedAt":61,"install":62,"resources":75},"01M71QFM7KWM540YR67FRN807Q","python-debugpy",null,"Debug Python: pdb REPL + debugpy remote (DAP).","gjbvsjjvgm-sketch\u002FWORM_V2_SOVEREIGN","gjbvsjjvgm-sketch","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F278511911?v=4","01M71F3JXKZQ70H2KCHHTXKZDA","01M71PENEVFN19DBZFNHJPCHQ7",0,"MIT","https:\u002F\u002Fgithub.com\u002Fgjbvsjjvgm-sketch\u002FWORM_V2_SOVEREIGN\u002Fblob\u002Fmain\u002Fcore\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 13:02:02",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","main","6746276ba18cb774853eb61b3b9399658bc9bc5c","core\u002Fskills\u002Fsoftware-development\u002Fpython-debugpy","https:\u002F\u002Fgithub.com\u002Fgjbvsjjvgm-sketch\u002FWORM_V2_SOVEREIGN","https:\u002F\u002Fgithub.com\u002Fgjbvsjjvgm-sketch","2026-04-28 17:01:03",{"source":8,"skillName":5,"commands":63,"prompt":73,"zipAvailable":74},[64,67,70],{"tool":65,"command":66},"npx","npx skills add gjbvsjjvgm-sketch\u002FWORM_V2_SOVEREIGN --skill python-debugpy",{"tool":68,"command":69},"bunx","bunx skills add gjbvsjjvgm-sketch\u002FWORM_V2_SOVEREIGN --skill python-debugpy",{"tool":71,"command":72},"pnpm","pnpm dlx skills add gjbvsjjvgm-sketch\u002FWORM_V2_SOVEREIGN --skill python-debugpy","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002Fgjbvsjjvgm-sketch\u002FWORM_V2_SOVEREIGN\u002Fblob\u002Fmain\u002Fcore\u002Fskills\u002Fsoftware-development\u002Fpython-debugpy\u002FSKILL.md",true,[],[77,116,138,154,173,192],{"skillNo":78,"name":79,"nameZh":6,"description":80,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":81,"categories":82,"keywords":95,"publishedAt":43},"01M71QG27G1R2H2ZWEG4S0WTZT","writing-plans","Write implementation plans: bite-sized tasks, paths, code.","https:\u002F\u002Fgithub.com\u002Fgjbvsjjvgm-sketch\u002FWORM_V2_SOVEREIGN\u002Fblob\u002Fmain\u002Fcore\u002Fskills\u002Fsoftware-development\u002Fwriting-plans\u002FSKILL.md",[83,88,91,92],{"categoryNo":84,"categoryName":85,"taxonomyType":86,"taxonomyTypeDesc":87},"01JBSEEDCAT000000000000015","视觉设计","SCENARIO","使用场景",{"categoryNo":89,"categoryName":90,"taxonomyType":86,"taxonomyTypeDesc":87},"01JBSEEDCAT000000000000018","写作与文案",{"categoryNo":18,"categoryName":19,"taxonomyType":20,"taxonomyTypeDesc":21},{"categoryNo":93,"categoryName":94,"taxonomyType":20,"taxonomyTypeDesc":21},"01JBSEEDCAT000000000000036","内容创作者",[96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115],"writing","writing plans","plans","test-driven","test-driven-development","superpowers","design","workflow","code","tasks","subagent","code-review","review","documentation","write","paths","planning","development","test","implementation",{"skillNo":117,"name":100,"nameZh":6,"description":118,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":119,"categories":120,"keywords":127,"publishedAt":43},"01M71QFZYRTJXMAE551MRAKZ14","TDD: enforce RED-GREEN-REFACTOR, tests before code.","https:\u002F\u002Fgithub.com\u002Fgjbvsjjvgm-sketch\u002FWORM_V2_SOVEREIGN\u002Fblob\u002Fmain\u002Fcore\u002Fskills\u002Fsoftware-development\u002Ftest-driven-development\u002FSKILL.md",[121,124],{"categoryNo":122,"categoryName":123,"taxonomyType":86,"taxonomyTypeDesc":87},"01JBSEEDCAT000000000000007","代码审查",{"categoryNo":125,"categoryName":126,"taxonomyType":86,"taxonomyTypeDesc":87},"01JBSEEDCAT000000000000008","测试与质量",[128,113,114,129,130,131,132,101,96,37,104,106,36,133,32,134,135,98,136,137],"test driven development","driven","green","red-green-refactor","testing","refactor","enforce","tests","tdd","quality",{"skillNo":139,"name":32,"nameZh":6,"description":140,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":141,"categories":142,"keywords":144,"publishedAt":43},"01M71QFWR5Q8EYEQ6MCS3SM6MC","4-phase root cause debugging: understand bugs before fixing.","https:\u002F\u002Fgithub.com\u002Fgjbvsjjvgm-sketch\u002FWORM_V2_SOVEREIGN\u002Fblob\u002Fmain\u002Fcore\u002Fskills\u002Fsoftware-development\u002Fsystematic-debugging\u002FSKILL.md",[143],{"categoryNo":125,"categoryName":126,"taxonomyType":86,"taxonomyTypeDesc":87},[145,37,36,99,146,147,100,101,96,106,148,149,113,150,98,151,114,152,153,27],"systematic debugging","problem","problem-solving","phase","bugs","root cause","root-cause","investigation","understand",{"skillNo":155,"name":156,"nameZh":6,"description":157,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":158,"categories":159,"keywords":165,"publishedAt":43},"01M71QFS9S04MD8JP747W5WM0N","subagent-driven-development","Execute plans via delegate_task subagents (2-stage review).","https:\u002F\u002Fgithub.com\u002Fgjbvsjjvgm-sketch\u002FWORM_V2_SOVEREIGN\u002Fblob\u002Fmain\u002Fcore\u002Fskills\u002Fsoftware-development\u002Fsubagent-driven-development\u002FSKILL.md",[160,161,164],{"categoryNo":89,"categoryName":90,"taxonomyType":86,"taxonomyTypeDesc":87},{"categoryNo":162,"categoryName":163,"taxonomyType":86,"taxonomyTypeDesc":87},"01JBSEEDCAT000000000000023","Skill与智能体开发",{"categoryNo":93,"categoryName":94,"taxonomyType":20,"taxonomyTypeDesc":21},[166,106,113,129,99,100,167,168,101,96,103,104,107,108,169,170,98,171,172,114],"subagent driven development","delegate","delegation","subagents","parallel","stage","task",{"skillNo":174,"name":175,"nameZh":6,"description":176,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":177,"categories":178,"keywords":181,"publishedAt":43},"01M71QFQ1DBCVAZ511MXFE6T2V","requesting-code-review","Pre-commit review: security scan, quality gates, auto-fix.","https:\u002F\u002Fgithub.com\u002Fgjbvsjjvgm-sketch\u002FWORM_V2_SOVEREIGN\u002Fblob\u002Fmain\u002Fcore\u002Fskills\u002Fsoftware-development\u002Frequesting-code-review\u002FSKILL.md",[179,180],{"categoryNo":122,"categoryName":123,"taxonomyType":86,"taxonomyTypeDesc":87},{"categoryNo":18,"categoryName":19,"taxonomyType":20,"taxonomyTypeDesc":21},[182,183,104,108,99,100,184,185,101,186,96,187,188,106,107,189,190,113,191,98],"code review","requesting code review","fix","auto-fix","auto","security scan","commit","verification","security","scan",{"skillNo":193,"name":194,"nameZh":6,"description":195,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":196,"categories":197,"keywords":198,"publishedAt":43},"01M71QFJMTKHGWANW9XA84S2G1","plan","Plan mode: write markdown plan to .hermes\u002Fplans\u002F, no exec.","https:\u002F\u002Fgithub.com\u002Fgjbvsjjvgm-sketch\u002FWORM_V2_SOVEREIGN\u002Fblob\u002Fmain\u002Fcore\u002Fskills\u002Fsoftware-development\u002Fplan\u002FSKILL.md",[],[194,199,112,113,200,98,96,103,106,201,202,110,115,27,203,204,129],"plan-mode","mode","markdown","plan mode","hermes agent","exec"]