agentsclimarketplace

Debug py

Skill spotcircuit/rebar/.claude/skills/software-development/debug-py

Debug Python interactively via pdb (`breakpoint()`, `python -m pdb`, post-mortem) or remote-attach via debugpy/remote-pdb. Use when print/logging is insufficient — set breakpoints, step, walk the stack, dump locals, evaluate expressions, attach to a running daemon, or post-mortem an exception. Complements superpowers:systematic-debugging (which is the general process; this is the Python-specific tooling reference).From its SKILL.md

Install
npx -y skills add spotcircuit/rebar --skill debug-py

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • 3 stars3 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

SKILL.md

7.2 KB, as published. Nobody here has run it

debug-py

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 (worker, daemon) misbehaves and can't be restarted.
  • Post-mortem: an exception fired and you want the locals at the crash site.
  • A subprocess / child worker is the actual bug site.

Don't use for: anything print() / logging.debug / pytest -vv --tb=long --showlocals already reveals.

Related skills

  • superpowers:systematic-debugging — the general debugging process. This skill is the Python-side tooling.
  • debug-node — Node.js equivalent.

Pick one

ToolWhen
breakpoint() + pdbLocal, interactive, simplest. Edit the source, run normally.
python -m pdb script.pyLaunch under pdb with no source edits.
debugpyRemote / headless / attach-to-running. Talks DAP.
remote-pdbCleanest agent-friendly remote pdb over a TCP socket. Usually what you want from a terminal.

Start with breakpoint().

pdb reference

Inside (Pdb):

CommandAction
h / h cmdhelp
nnext line (step over)
sstep into
rreturn from current function
ccontinue
unt Ncontinue until line N
j Njump to line N (same function only)
l / lllist source / full function
wwhere (stack trace)
u / dmove up / down stack
aprint args of current function
p expr / pp exprprint / pretty-print
display exprauto-print on every stop
b file:linebreakpoint
b funcbreak on function entry
b file:line, condconditional breakpoint
cl Nclear breakpoint N
tbreak file:lineone-shot breakpoint
!stmtexecute arbitrary Python (assignments included)
interactdrop into full Python REPL in current scope (Ctrl+D to exit)
qquit

interact is the most powerful: import anything, call mutating methods, inspect deeply.

Recipe 1: local breakpoint

def compute(x, y):
    result = some_helper(x)
    breakpoint()           # drops into pdb
    return result + y

Run normally. Strip before committing:

rg -n 'breakpoint\(\)' --type py

Recipe 2: launch a script under pdb

python -m pdb path/to/script.py arg1 arg2
(Pdb) b path/to/script.py:42
(Pdb) c

Recipe 3: pytest

# Drop to pdb on failure
pytest tests/path/to/test_file.py::test_name --pdb

# Drop to pdb at start of test
pytest tests/path/to/test_file.py::test_name --trace

# Show locals in tracebacks without pdb
pytest tests/path/to/test_file.py --showlocals --tb=long

pdb does NOT work under pytest-xdist. If your suite runs -n auto, add -p no:xdist or -n 0:

pytest tests/foo_test.py::test_bar --pdb -p no:xdist

Recipe 4: post-mortem on any exception

import pdb, sys
try:
    run_the_thing()
except Exception:
    pdb.post_mortem(sys.exc_info()[2])

Or wrap a whole script:

python -m pdb -c continue script.py
# On crash, pdb lands at the exception frame

Global hook (REPL/Jupyter):

import sys
def excepthook(etype, value, tb):
    import pdb; pdb.post_mortem(tb)
sys.excepthook = excepthook

Recipe 5: remote debug with debugpy

For long-lived processes you can't restart cleanly.

Setup

pip install debugpy

Pattern A — source-edit, wait at launch

import debugpy
debugpy.listen(("127.0.0.1", 5678))
print("debugpy listening on 5678, waiting for client...", flush=True)
debugpy.wait_for_client()
debugpy.breakpoint()  # optional: pause once attached

Pattern B — no source edit, launch with -m debugpy

python -m debugpy --listen 127.0.0.1:5678 --wait-for-client your_script.py arg1
python -m debugpy --listen 127.0.0.1:5678 --wait-for-client -m your.module

Pattern C — attach to a running PID

python -m debugpy --listen 127.0.0.1:5678 --pid <pid>

Hardened kernels block ptrace injection. Workaround:

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

Connecting

VS Code / Cursor / Zed launch.json:

{
  "name": "Attach Python",
  "type": "debugpy",
  "request": "attach",
  "connect": { "host": "127.0.0.1", "port": 5678 },
  "justMyCode": false
}

Recipe 6: remote-pdb (agent-friendly)

When you want a real (Pdb) prompt over TCP, no DAP overhead:

pip install remote-pdb
from remote_pdb import set_trace
set_trace(host="127.0.0.1", port=4444)   # blocks until connection

Connect from another terminal:

nc 127.0.0.1 4444

For terminal-only debugging from inside a coding agent, prefer remote-pdb over debugpy.

Common pitfalls

  1. pdb under pytest-xdist silently does nothing. Always use -p no:xdist or -n 0.
  2. breakpoint() in CI / non-TTY hangs the process. Never commit it. Add a pre-commit grep.
  3. PYTHONBREAKPOINT=0 disables all breakpoint() calls. echo $PYTHONBREAKPOINT if your breakpoint isn't hitting.
  4. debugpy.listen doesn't block unless you also call wait_for_client(). Without it, your first breakpoint may fire before the client attaches.
  5. PID attach fails on hardened kernels. ptrace_scope=1 (Ubuntu default) blocks cross-process ptrace. Either lower it or launch under debugpy from the start.
  6. Threads. pdb only debugs the current thread. For multithreaded code, use debugpy or threading.settrace() per thread.
  7. asyncio. pdb works in coroutines, but await inside pdb requires Python 3.13+ — on 3.11/3.12 use !stmt-based awaits via asyncio.ensure_future.
  8. Forking / multiprocessing. pdb does not follow forks. Each child needs its own breakpoint() / set_trace().

Verification checklist

  • python -c "import debugpy; print(debugpy.__version__)" works after install.
  • Port is listening: ss -tlnp | grep 5678.
  • First breakpoint actually hits (else: PYTHONBREAKPOINT=0, xdist, or finished before attach).
  • where / w shows the expected stack.
  • No stray breakpoint() / set_trace() left committed:
    rg -n 'breakpoint\(\)|set_trace\(|debugpy\.listen' --type py
    

One-shot recipes

"Why is this dict missing a key?"

breakpoint()
# (Pdb)
pp d
pp list(d.keys())
w               # how did we get here

"Test passes in isolation, fails in the suite."

pytest tests/the_test.py --pdb -p no:xdist
# If only fails alongside others:
pytest tests/ -x --pdb -p no:xdist

"Async handler deadlocks."

import remote_pdb; remote_pdb.set_trace(host="127.0.0.1", port=4444)

Trigger the handler, then nc 127.0.0.1 4444, then w and !import asyncio; asyncio.all_tasks().

"Post-mortem a subprocess crash."

PYTHONFAULTHANDLER=1 python -m pdb -c continue path/to/entrypoint.py

Keep looking

Skills are one crate of 627,801. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.