[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M74FTK56E80NFETBVZGS70X5":3,"skill-same:01M74FTK56E80NFETBVZGS70X5":62},{"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":17,"publishedAt":18,"linkOnly":19,"markdownContent":20,"markdownBody":21,"frontMatter":22,"refName":42,"commitSha":43,"skillDirectory":44,"forksCount":13,"repositoryHtmlUrl":45,"repositoryHomepageUrl":6,"ownerHtmlUrl":46,"ownerBlogUrl":6,"ownerDescription":6,"updatedAt":47,"install":48,"resources":61},"01M74FTK56E80NFETBVZGS70X5","python-debugpy",null,"Debug Python: pdb REPL + debugpy remote (DAP).","lzy18001500226\u002FMoSim","lzy18001500226","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F152623587?v=4","01M71ZMEX36NK05B642131DEEK","01M71ZMEXSMX72KPVNJ572G0CM",0,"MIT","https:\u002F\u002Fgithub.com\u002Flzy18001500226\u002FMoSim\u002Fblob\u002Fmain\u002FReferences\u002FAgent\u002FPlatforms\u002Fhermes-agent\u002Fskills\u002Fsoftware-development\u002Fpython-debugpy\u002FSKILL.md",[],[],"2026-11-11 15:11:14",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, 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":23,"license":14,"version":24,"metadata":25,"platforms":39,"description":7},"Hermes Agent","1.0.0",{"hermes":26},{"tags":27,"related_skills":35},[28,29,30,31,32,33,34],"debugging","python","pdb","debugpy","breakpoints","dap","post-mortem",[36,37,38],"systematic-debugging","node-inspect-debugger","debugging-hermes-tui-commands",[40,41],"linux","macos","main","e0ec177d00f087cb04e90206b2f7d3aa2f5a1350","References\u002FAgent\u002FPlatforms\u002Fhermes-agent\u002Fskills\u002Fsoftware-development\u002Fpython-debugpy","https:\u002F\u002Fgithub.com\u002Flzy18001500226\u002FMoSim","https:\u002F\u002Fgithub.com\u002Flzy18001500226","2026-08-31 23:29:00",{"source":8,"skillName":5,"commands":49,"prompt":59,"zipAvailable":60},[50,53,56],{"tool":51,"command":52},"npx","npx skills add lzy18001500226\u002FMoSim --skill python-debugpy",{"tool":54,"command":55},"bunx","bunx skills add lzy18001500226\u002FMoSim --skill python-debugpy",{"tool":57,"command":58},"pnpm","pnpm dlx skills add lzy18001500226\u002FMoSim --skill python-debugpy","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002Flzy18001500226\u002FMoSim\u002Fblob\u002Fmain\u002FReferences\u002FAgent\u002FPlatforms\u002Fhermes-agent\u002Fskills\u002Fsoftware-development\u002Fpython-debugpy\u002FSKILL.md",true,[],[63,73,81,89,97,105],{"skillNo":64,"name":65,"nameZh":6,"description":66,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":67,"licenseFileUrl":68,"sourceHtmlUrl":69,"categories":70,"keywords":71,"publishedAt":72},"01M78RK6JGRPTD3TDSMTQWDQ9P","skill-installer","Install Codex skills into $CODEX_HOME\u002Fskills from a curated list or a GitHub repo path. Use when a user asks to list installable skills, install a curated skill, or install a skill from another repo (including private repos).","Apache-2.0","https:\u002F\u002Fgithub.com\u002Flzy18001500226\u002FMoSim\u002Fblob\u002Fmain\u002Fsrc\u002FAgent\u002Fcodex-main\u002Fcodex-rs\u002Fskills\u002Fsrc\u002Fassets\u002Fsamples\u002Fskill-installer\u002FLICENSE.txt","https:\u002F\u002Fgithub.com\u002Flzy18001500226\u002FMoSim\u002Fblob\u002Fmain\u002Fsrc\u002FAgent\u002Fcodex-main\u002Fcodex-rs\u002Fskills\u002Fsrc\u002Fassets\u002Fsamples\u002Fskill-installer\u002FSKILL.md",[],[],"2026-11-15 14:19:17",{"skillNo":74,"name":75,"nameZh":6,"description":76,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":67,"licenseFileUrl":77,"sourceHtmlUrl":78,"categories":79,"keywords":80,"publishedAt":72},"01M78RK29KVRDQXSMVZX1GQTJN","skill-creator","Guide for creating effective skills. This skill should be used when users want to create a new skill (or update an existing skill) that extends Codex's capabilities with specialized knowledge, workflows, or tool integrations.","https:\u002F\u002Fgithub.com\u002Flzy18001500226\u002FMoSim\u002Fblob\u002Fmain\u002Fsrc\u002FAgent\u002Fcodex-main\u002Fcodex-rs\u002Fskills\u002Fsrc\u002Fassets\u002Fsamples\u002Fskill-creator\u002Flicense.txt","https:\u002F\u002Fgithub.com\u002Flzy18001500226\u002FMoSim\u002Fblob\u002Fmain\u002Fsrc\u002FAgent\u002Fcodex-main\u002Fcodex-rs\u002Fskills\u002Fsrc\u002Fassets\u002Fsamples\u002Fskill-creator\u002FSKILL.md",[],[],{"skillNo":82,"name":83,"nameZh":6,"description":84,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":67,"licenseFileUrl":85,"sourceHtmlUrl":86,"categories":87,"keywords":88,"publishedAt":72},"01M78RJPF3210Y5DA9VJ6JMMG6","openai-docs","Use for Codex models\u002Fpricing, scheduled tasks, skills, settings, setup, troubleshooting, customization, automations, and self-knowledge—including 'you,' 'your,' 'this app,' or 'this coding agent' when they refer to Codex—and for OpenAI APIs\u002Fproducts and ChatGPT Work. Also use for model choice\u002Fmigration, prompting, SDKs, Responses, Realtime, agents, evals, and Chat\u002FWork\u002FCodex comparisons. Do not use for generic app\u002Fsoftware tasks that merely mention Codex.","https:\u002F\u002Fgithub.com\u002Flzy18001500226\u002FMoSim\u002Fblob\u002Fmain\u002Fsrc\u002FAgent\u002Fcodex-main\u002Fcodex-rs\u002Fskills\u002Fsrc\u002Fassets\u002Fsamples\u002Fopenai-docs\u002FLICENSE.txt","https:\u002F\u002Fgithub.com\u002Flzy18001500226\u002FMoSim\u002Fblob\u002Fmain\u002Fsrc\u002FAgent\u002Fcodex-main\u002Fcodex-rs\u002Fskills\u002Fsrc\u002Fassets\u002Fsamples\u002Fopenai-docs\u002FSKILL.md",[],[],{"skillNo":90,"name":91,"nameZh":6,"description":92,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":67,"licenseFileUrl":93,"sourceHtmlUrl":94,"categories":95,"keywords":96,"publishedAt":72},"01M78RJH7X67C5AY8ZVS7RQNWJ","imagegen","Generate or edit raster images when the task benefits from AI-created bitmap visuals such as photos, illustrations, textures, sprites, mockups, or transparent-background cutouts. Use when Codex should create a brand-new image, transform an existing image, or derive visual variants from references, and the output should be a bitmap asset rather than repo-native code or vector. Do not use when the task is better handled by editing existing SVG\u002Fvector\u002Fcode-native assets, extending an established icon or logo system, or building the visual directly in HTML\u002FCSS\u002Fcanvas.","https:\u002F\u002Fgithub.com\u002Flzy18001500226\u002FMoSim\u002Fblob\u002Fmain\u002Fsrc\u002FAgent\u002Fcodex-main\u002Fcodex-rs\u002Fskills\u002Fsrc\u002Fassets\u002Fsamples\u002Fimagegen\u002FLICENSE.txt","https:\u002F\u002Fgithub.com\u002Flzy18001500226\u002FMoSim\u002Fblob\u002Fmain\u002Fsrc\u002FAgent\u002Fcodex-main\u002Fcodex-rs\u002Fskills\u002Fsrc\u002Fassets\u002Fsamples\u002Fimagegen\u002FSKILL.md",[],[],{"skillNo":98,"name":99,"nameZh":6,"description":100,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":67,"licenseFileUrl":101,"sourceHtmlUrl":102,"categories":103,"keywords":104,"publishedAt":72},"01M78RBBAYMJ6T3ZMAYHCFM6G7","skill-creator-cc","Create new skills, modify and improve existing skills, and measure skill performance. Use when users want to create a skill from scratch, update or optimize an existing skill, run evals to test a skill, benchmark skill performance with variance analysis, or optimize a skill's description for better triggering accuracy.","https:\u002F\u002Fgithub.com\u002Flzy18001500226\u002FMoSim\u002Fblob\u002Fmain\u002FReferences\u002FAgent\u002FWorkflow\u002Fokwinds\u002Fmiscellany\u002Fagent\u002Fskills\u002Fskill-creator-cc\u002FLICENSE.txt","https:\u002F\u002Fgithub.com\u002Flzy18001500226\u002FMoSim\u002Fblob\u002Fmain\u002FReferences\u002FAgent\u002FWorkflow\u002Fokwinds\u002Fmiscellany\u002Fagent\u002Fskills\u002Fskill-creator-cc\u002FSKILL.md",[],[],{"skillNo":106,"name":107,"nameZh":6,"description":108,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":109,"sourceHtmlUrl":110,"categories":111,"keywords":112,"publishedAt":113},"01M76VV9V2R9FZTVGMJZV7Y996","prd-writing-guide","Write complete, unambiguous PRDs that development teams can implement without guesswork. Includes requirement discovery framework, structured documentation methodology, completeness checklists, and common pitfall avoidance. Use when: writing new PRDs, reviewing PRD drafts, validating requirement completeness, preparing for engineering handoff. Triggers: 'write PRD', '写PRD', '产品需求文档', '需求文档', '需求规格', '需求评审', '完善需求', 'create requirements doc', 'product requirements', 'feature spec', 'requirements document'. Anti-triggers: 'technical design doc', 'architecture design', 'implementation plan', 'API design', '架构设计', '技术方案', '实现方案', '接口设计'.","https:\u002F\u002Fgithub.com\u002Flzy18001500226\u002FMoSim\u002Fblob\u002Fmain\u002FReferences\u002FAgent\u002FWorkflow\u002Fokwinds\u002Fmiscellany\u002Fagent\u002Fskills\u002Fprd-writing-guide\u002FLICENSE","https:\u002F\u002Fgithub.com\u002Flzy18001500226\u002FMoSim\u002Fblob\u002Fmain\u002FReferences\u002FAgent\u002FWorkflow\u002Fokwinds\u002Fmiscellany\u002Fagent\u002Fskills\u002Fprd-writing-guide\u002FSKILL.md",[],[],"2026-11-12 13:55:43"]