# Python Debugpy

> Debug Python: pdb REPL + debugpy remote (DAP).

- **Type:** Skill
- **Install:** `agentstack add skill-shikanime-labs-skills-python-debugpy`
- **Verified:** Yes — security-reviewed for prompt injection and unsafe behavior
- **Seller:** [shikanime-labs](https://agentstack.voostack.com/s/shikanime-labs)
- **Installs:** 0
- **Category:** [Agent Skills](https://agentstack.voostack.com/c/agent-skills)
- **Latest version:** 0.1.0
- **License:** Apache-2.0
- **Upstream author:** [shikanime-labs](https://github.com/shikanime-labs)
- **Source:** https://github.com/shikanime-labs/skills/tree/main/skills/hermes/python-debugpy

## Install

```sh
agentstack add skill-shikanime-labs-skills-python-debugpy
```

Requires the [AgentStack CLI](https://agentstack.voostack.com/docs/cli). Works with Claude Code, Cursor, and any MCP-compatible agent.

## About

# Python Debugger (pdb + debugpy)

## Overview

Three tools, picked by situation:

| Tool                     | When                                                                                                                                                          |
| ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`breakpoint()` + pdb** | Local, interactive, simplest. Add `breakpoint()` in the source, run normally, get a REPL at that line.                                                        |
| **`python -m pdb`**      | Launch an existing script under pdb with no source edits. Useful for quick poking.                                                                            |
| **`debugpy`**            | Remote / headless / "attach to already-running process." Talks DAP, scriptable from terminal, works for long-lived processes (gateway, daemon, PTY children). |

**Start with `breakpoint()`.** It's the cheapest thing that works.

## When to Use

- A test fails and the traceback doesn't reveal why a value is wrong
- You need to step through a function and watch a collection mutate
- A long-running process (hermes gateway, tui_gateway) misbehaves and you can't
  restart it
- Post-mortem: an exception fired in prod-ish code and you want to inspect
  locals at the crash site
- A subprocess / child (Python `_SlashWorker`, PTY bridge worker) is the actual
  bug site

**Don't use for:** things `print()` / `logging.debug` solve in under a minute,
or things `pytest -vv --tb=long --showlocals` already reveals.

## pdb Quick Reference

Inside any pdb prompt (`(Pdb)`):

| Command              | Action                                                       |
| -------------------- | ------------------------------------------------------------ |
| `h` / `h cmd`        | help                                                         |
| `n`                  | next line (step over)                                        |
| `s`                  | step into                                                    |
| `r`                  | return from current function                                 |
| `c`                  | continue                                                     |
| `unt N`              | continue until line N                                        |
| `j N`                | jump to line N (same function only)                          |
| `l` / `ll`           | list source around current line / full function              |
| `w`                  | where (stack trace)                                          |
| `u` / `d`            | move up / down in the stack                                  |
| `a`                  | print args of the current function                           |
| `p expr` / `pp expr` | print / pretty-print expression                              |
| `display expr`       | auto-print expr on every stop                                |
| `b file:line`        | set breakpoint                                               |
| `b func`             | break on function entry                                      |
| `b file:line, cond`  | conditional breakpoint                                       |
| `cl N`               | clear breakpoint N                                           |
| `tbreak file:line`   | one-shot breakpoint                                          |
| `!stmt`              | execute arbitrary Python (assignments included)              |
| `interact`           | drop into full Python REPL in current scope (Ctrl+D to exit) |
| `q`                  | quit                                                         |

The `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.

## Recipe 1: Local breakpoint

Easiest. Edit the file:

```python
def compute(x, y):
    result = some_helper(x)
    breakpoint()           # 
# debugpy injects itself into the process. Then attach a client as below.
```

Some kernels/security configs block the ptrace-based injection
(`/proc/sys/kernel/yama/ptrace_scope`). Fix with:

```bash
echo 0 | sudo tee /proc/sys/kernel/yama/ptrace_scope
```

### Connecting a client from the terminal

The easiest terminal-side DAP client is VS Code CLI or a small script. From
inside Hermes you have two practical options:

**Option 1: `debugpy`'s own CLI REPL** — not an official feature, but a tiny DAP
client script:

```python
# /tmp/dap_client.py
import socket, json, itertools, time, sys

HOST, PORT = "127.0.0.1", 5678
s = socket.create_connection((HOST, PORT))
seq = itertools.count(1)

def send(msg):
    msg["seq"] = next(seq)
    body = json.dumps(msg).encode()
    s.sendall(f"Content-Length: {len(body)}\r\n\r\n".encode() + body)

def recv():
    header = b""
    while b"\r\n\r\n" not in header:
        header += s.recv(1)
    length =
      int(header.decode().split("Content-Length:")[1].split("\r\n")[0].strip())
    body = b""
    while len(body)  /proc/sys/kernel/yama/ptrace_scope` (needs root) or launch under
   `debugpy` from the start.

6. **Threads.** `pdb` only debugs the current thread. For multithreaded code,
   use `debugpy` (thread-aware DAP) or set `threading.settrace()` per thread.

7. **asyncio.** `pdb` works in coroutines but `await` inside pdb requires Python
   3.13+ or `await` from `interact` mode on older versions. For 3.11/3.12, use
   `asyncio.run_coroutine_threadsafe` tricks or `!stmt`-based awaits via
   `asyncio.ensure_future`.

8. **`scripts/run_tests.sh` strips credentials and sets `HOME=`.** 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.

9. **Forking / multiprocessing.** pdb does not follow forks. Each child needs
   its own `breakpoint()` or `set_trace()`. For Hermes subagents, debug one
   process at a time.

## Verification Checklist

- [ ] After `pip install debugpy`, confirm:
      `python -c "import debugpy; print(debugpy.__version__)"`
- [ ] For remote debug, confirm the port is actually listening:
      `ss -tlnp | grep 5678`
- [ ] First breakpoint actually hits (if it doesn't, you likely have
      `PYTHONBREAKPOINT=0`, you're under xdist, or execution finished before
      attach)
- [ ] `where` / `w` shows the expected call stack
- [ ] Post-debug cleanup: no stray `breakpoint()` / `set_trace()` in committed
      code

  ```bash
  rg -n 'breakpoint\(\)|set_trace\(|debugpy\.listen' --type py
  ```

## One-Shot Recipes

## "Why is this dict missing a key?"

```python
# add above the KeyError site
breakpoint()
# then in pdb:
(Pdb) pp d
(Pdb) pp list(d.keys())
(Pdb) w                # how did we get here
```

## "This test passes in isolation but fails in the suite."

```bash
scripts/run_tests.sh tests/the_test.py --pdb -p no:xdist
# But if it only fails WITH other tests:
source .venv/bin/activate
python -m pytest tests/ -x --pdb -p no:xdist
# Now it pdb-traps at the exact failing test after state accumulated.
```

## "My async handler deadlocks."

```python
# Add at handler entry
import remote_pdb; remote_pdb.set_trace(host="127.0.0.1", port=4444)
```

Trigger 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.

## "Post-mortem on a crash in an Ink child process / subprocess."

```bash
PYTHONFAULTHANDLER=1 python -m pdb -c continue path/to/entrypoint.py
# On crash, pdb lands at the frame of the exception with full locals
```

## Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

- **Author:** [shikanime-labs](https://github.com/shikanime-labs)
- **Source:** [shikanime-labs/skills](https://github.com/shikanime-labs/skills)
- **License:** Apache-2.0

Install and usage instructions live in the source repository linked above.

## Pricing

- **Free** — Free

## Security capabilities

Automated source analysis of v0.1.0 — what this tool can access:

- **Network access:** no
- **Filesystem access:** no
- **Shell / process execution:** yes
- **Environment & secrets:** no
- **Dynamic code execution:** no

*"Yes" means the capability is present in the source — more access means more to trust, not that it is unsafe.*


## Versions

- **0.1.0** — security scan: passed — Imported from the upstream source.

## Links

- Listing page: https://agentstack.voostack.com/l/skill-shikanime-labs-skills-python-debugpy
- Seller: https://agentstack.voostack.com/s/shikanime-labs
- Browse the marketplace: https://agentstack.voostack.com/browse

---
Listed on AgentStack — the marketplace for AI agent skills and MCP servers. Every listing is security-reviewed. Creators keep 70%.
