Python’s pdb module is the interactive source-code debugger. import pdb. set_trace: pdb.set_trace() — drop into the debugger at that line (pre-3.7). breakpoint(): built-in breakpoint() (Python 3.7+) calls pdb.set_trace() by default; override with PYTHONBREAKPOINT=mymod.trace. run: pdb.run("my_function()") — debug a code string. runcall: pdb.runcall(fn, *args) — debug a function call. post_mortem: pdb.post_mortem(tb) — inspect a traceback object; pdb.pm() — debug the last exception’s tb. Key commands: n (next line), s (step into), r (return from function), c (continue), q (quit), l / ll (list source), p expr (print expression), pp expr (pretty-print), w (where — stack), u/d (up/down frames), b lineno (breakpoint), b func (function breakpoint), cl n (clear breakpoint n), !stmt (execute statement). Pdb class: dbg = pdb.Pdb(stdin=..., stdout=...) for custom I/O. Conditional breakpoints: (Pdb) b mymod.py:42, x > 10. Commands hook: (Pdb) commands 1 \n p x \n end. Claude Code generates automated post-mortem reporters, test failure investigators, headless debug trace collectors, and exception-context extractors.
CLAUDE.md for pdb
## pdb Stack
- Stdlib: import pdb
- Break: breakpoint() # Python 3.7+ built-in, calls pdb
- PostM: pdb.post_mortem() # inspect last exception
- API: pdb.runcall(fn, args) # debug a function call
- CI: PYTHONBREAKPOINT=0 # disable all breakpoints in CI
- Key cmds: n s c r q w u d p pp l ll b cl
pdb Debugging Utilities Pipeline
# app/pdbutil.py — post-mortem, trace capture, exception context, headless
from __future__ import annotations
import io
import pdb
import sys
import traceback
from contextlib import contextmanager
from dataclasses import dataclass, field
from typing import Any, Callable, Generator, TypeVar
T = TypeVar("T")
# ─────────────────────────────────────────────────────────────────────────────
# 1. Post-mortem helpers
# ─────────────────────────────────────────────────────────────────────────────
def post_mortem_on_exception(fn: Callable[..., T], *args: Any, **kwargs: Any) -> T:
"""
Run fn(*args, **kwargs). If it raises, drop into pdb post-mortem.
Useful for running scripts interactively with automatic exception inspection.
Example:
post_mortem_on_exception(my_heavy_job, data=records)
"""
try:
return fn(*args, **kwargs)
except Exception:
tb = sys.exc_info()[2]
pdb.post_mortem(tb)
raise
@contextmanager
def debug_on_exception() -> Generator[None, None, None]:
"""
Context manager that activates pdb post-mortem on any unhandled exception.
Example:
with debug_on_exception():
risky_operation()
"""
try:
yield
except Exception:
tb = sys.exc_info()[2]
pdb.post_mortem(tb)
raise
def install_pm_hook() -> None:
"""
Install a global exception hook that starts pdb post-mortem on any
unhandled exception in the main thread. Call once at startup in dev mode.
Example:
if os.environ.get("DEBUG"):
install_pm_hook()
"""
def _hook(exc_type, exc_value, exc_tb):
if issubclass(exc_type, (KeyboardInterrupt, SystemExit)):
sys.__excepthook__(exc_type, exc_value, exc_tb)
return
traceback.print_exception(exc_type, exc_value, exc_tb)
print("\nEntering post-mortem debugger...", file=sys.stderr)
pdb.post_mortem(exc_tb)
sys.excepthook = _hook
def uninstall_pm_hook() -> None:
"""Restore the default sys.excepthook."""
sys.excepthook = sys.__excepthook__
# ─────────────────────────────────────────────────────────────────────────────
# 2. Headless PDB trace capture (no interactive TTY required)
# ─────────────────────────────────────────────────────────────────────────────
@dataclass
class TraceCapture:
"""
Run a function under pdb and capture the debugger's stdout output as a string.
The debugger runs a sequence of commands automatically instead of waiting for input.
Useful for automated trace collection in tests and CI.
Example:
def buggy(x):
a = x * 2
b = a + 1
return b
report = TraceCapture.run(buggy, 5, commands=["n", "n", "p a", "p b", "q"])
print(report.output)
"""
output: str
commands: list[str]
@classmethod
def run(
cls,
fn: Callable,
*args: Any,
commands: list[str] | None = None,
**kwargs: Any,
) -> "TraceCapture":
cmds = commands or ["n", "n", "n", "p locals()", "q"]
stdin_buf = io.StringIO("\n".join(cmds) + "\nq\n")
stdout_buf = io.StringIO()
debugger = pdb.Pdb(stdin=stdin_buf, stdout=stdout_buf)
try:
debugger.runcall(fn, *args, **kwargs)
except (SystemExit, BdbQuit := __import__("bdb").BdbQuit):
pass
except Exception:
pass
return cls(output=stdout_buf.getvalue(), commands=cmds)
# ─────────────────────────────────────────────────────────────────────────────
# 3. Exception context extraction (without interactive pdb)
# ─────────────────────────────────────────────────────────────────────────────
@dataclass
class FrameLocals:
"""Locals snapshot from one frame of a traceback."""
filename: str
lineno: int
name: str
line: str
locals: dict[str, str] # {var_name: safe_repr}
@dataclass
class ExceptionContext:
exc_type: str
exc_value: str
frames: list[FrameLocals]
@classmethod
def capture(cls, exc: BaseException | None = None) -> "ExceptionContext":
"""
Capture local variables from every frame in the current or given exception.
Example:
try:
risky()
except Exception as e:
ctx = ExceptionContext.capture(e)
for frame in ctx.frames:
print(f" {frame.name}:{frame.lineno} → {frame.locals}")
"""
import reprlib
if exc is None:
_, exc_val, tb = sys.exc_info()
exc = exc_val
else:
tb = exc.__traceback__
if exc is None:
return cls(exc_type="None", exc_value="", frames=[])
r = reprlib.Repr()
r.maxstring = 60
r.maxother = 40
r.maxlevel = 2
frames: list[FrameLocals] = []
while tb is not None:
frame = tb.tb_frame
frames.append(FrameLocals(
filename=frame.f_code.co_filename,
lineno=tb.tb_lineno,
name=frame.f_code.co_name,
line=traceback.extract_tb(tb, limit=1)[0].line or "",
locals={k: r.repr(v) for k, v in frame.f_locals.items()},
))
tb = tb.tb_next
return cls(
exc_type=type(exc).__name__,
exc_value=str(exc),
frames=frames,
)
def summary(self) -> str:
lines = [f"{self.exc_type}: {self.exc_value}"]
for f in self.frames:
lines.append(f" File {f.filename!r}, line {f.lineno}, in {f.name}")
if f.line:
lines.append(f" {f.line.strip()}")
if f.locals:
for k, v in list(f.locals.items())[:6]:
lines.append(f" {k} = {v}")
return "\n".join(lines)
# ─────────────────────────────────────────────────────────────────────────────
# 4. Safe execution with timeout and debug fallback
# ─────────────────────────────────────────────────────────────────────────────
def safe_exec(fn: Callable[..., T], *args: Any, debug: bool = False, **kwargs: Any) -> tuple[T | None, ExceptionContext | None]:
"""
Execute fn; return (result, None) on success or (None, ExceptionContext) on failure.
If debug=True and stdin is a tty, drop into pdb post-mortem.
Example:
result, err = safe_exec(parse_record, raw_line)
if err:
print(err.summary())
"""
try:
return fn(*args, **kwargs), None
except Exception as exc:
ctx = ExceptionContext.capture(exc)
if debug and sys.stdin.isatty():
pdb.post_mortem(exc.__traceback__)
return None, ctx
# ─────────────────────────────────────────────────────────────────────────────
# 5. Conditional breakpoint helpers
# ─────────────────────────────────────────────────────────────────────────────
_bp_hit_counts: dict[str, int] = {}
def breakpoint_if(condition: bool, label: str = "") -> None:
"""
Call pdb.set_trace() only if condition is True.
In CI (PYTHONBREAKPOINT=0 or non-tty), silently skips.
Example:
breakpoint_if(x < 0, "negative_x")
"""
if condition and sys.stdin.isatty() and os.environ.get("PYTHONBREAKPOINT") != "0":
pdb.set_trace()
def breakpoint_on_nth(label: str, n: int) -> None:
"""
Break into pdb on the nth call to this function with this label.
Useful for debugging loops where a bug appears on a specific iteration.
Example:
for i, record in enumerate(records):
breakpoint_on_nth("record_loop", 50) # break on 50th iteration
process(record)
"""
import os
_bp_hit_counts[label] = _bp_hit_counts.get(label, 0) + 1
if _bp_hit_counts[label] == n and sys.stdin.isatty() and os.environ.get("PYTHONBREAKPOINT") != "0":
pdb.set_trace()
# ─────────────────────────────────────────────────────────────────────────────
# Demo
# ─────────────────────────────────────────────────────────────────────────────
if __name__ == "__main__":
import os
print("=== pdb demo ===")
# ── TraceCapture (headless) ───────────────────────────────────────────────
print("\n--- TraceCapture (headless pdb run) ---")
def example_fn(x: int, y: int) -> int:
a = x * 2
b = a + y
return b
report = TraceCapture.run(example_fn, 3, 7, commands=["n", "n", "n", "p a", "p b", "c"])
print(" pdb trace output:")
for line in report.output.splitlines()[:15]:
print(f" {line}")
# ── ExceptionContext ──────────────────────────────────────────────────────
print("\n--- ExceptionContext.capture ---")
def outer(val):
def inner(v):
return 1 / v # raises on 0
return inner(val - val) # always 0
_, err = safe_exec(outer, 5)
if err:
print(err.summary())
# ── safe_exec ─────────────────────────────────────────────────────────────
print("\n--- safe_exec success ---")
result, err = safe_exec(lambda x: x * 2, 21)
print(f" result={result} err={err}")
print("\n--- safe_exec failure ---")
result, err = safe_exec(int, "not_a_number")
if err:
print(f" {err.exc_type}: {err.exc_value}")
# ── install_pm_hook (demonstrate registration, not trigger) ───────────────
print("\n--- install_pm_hook / uninstall_pm_hook ---")
install_pm_hook()
print(f" sys.excepthook is custom: {sys.excepthook is not sys.__excepthook__}")
uninstall_pm_hook()
print(f" after uninstall, restored: {sys.excepthook is sys.__excepthook__}")
print("\n=== done (no interactive pdb invoked) ===")
For the ipdb alternative — ipdb (PyPI) provides the same pdb interface but replaces the plain Python REPL with an IPython shell, giving syntax highlighting, tab completion, ? help, and %timeit magic commands; it drops in as a direct replacement: import ipdb; ipdb.set_trace() — use ipdb in interactive development when working in an IPython-aware environment; use pdb when you need zero dependencies, are running in a constrained environment (containers, CI), or are implementing tooling on top of the debugger programmatically. For the pudb / web_pdb alternative — pudb renders a full-screen TUI with source view, stack panel, and variable inspector using curses; web_pdb exposes a browser-based debugger interface accessible over HTTP — use pudb for a visual debugger experience in terminal sessions; use web_pdb for debugging remote processes (servers, containers) where you cannot attach a TTY; use stdlib pdb for automated trace collection, post-mortem in CI, and cases where a plain text interface is sufficient. The Claude Skills 360 bundle includes pdb skill sets covering post_mortem_on_exception()/debug_on_exception()/install_pm_hook()/uninstall_pm_hook() auto-debug helpers, TraceCapture for headless pdb execution with command replay, ExceptionContext/FrameLocals for programmatic frame-local capture, safe_exec() for result-plus-error pattern, and breakpoint_if()/breakpoint_on_nth() conditional break helpers. Start with the free tier to try Python debugging patterns and pdb pipeline code generation.