Claude Code for pdb: Python Interactive Debugger — Claude Skills 360 Blog
Blog / AI / Claude Code for pdb: Python Interactive Debugger
AI

Claude Code for pdb: Python Interactive Debugger

Published: August 30, 2028
Read time: 5 min read
By: Claude Skills 360

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.

Keep Reading

AI

Claude Code for email.contentmanager: Python Email Content Accessors

Read and write EmailMessage body content with Python's email.contentmanager module and Claude Code — email contentmanager ContentManager for the class that maps content types to get and set handler functions allowing EmailMessage to support get_content and set_content with type-specific behaviour, email contentmanager raw_data_manager for the ContentManager instance that handles raw bytes and str payloads without any conversion, email contentmanager content_manager for the standard ContentManager instance used by email.policy.default that intelligently handles text plain text html multipart and binary content types, email contentmanager get_content_text for the handler that returns the decoded text payload of a text-star message part as a str, email contentmanager get_content_binary for the handler that returns the raw decoded bytes payload of a non-text message part, email contentmanager get_data_manager for the get-handler lookup used by EmailMessage get_content to find the right reader function for the content type, email contentmanager set_content text for the handler that creates and sets a text part correctly choosing charset and transfer encoding, email contentmanager set_content bytes for the handler that creates and sets a binary part with base64 encoding and optional filename Content-Disposition, email contentmanager EmailMessage get_content for the method that reads the message body using the registered content manager handlers, email contentmanager EmailMessage set_content for the method that sets the message body and MIME headers in one call, email contentmanager EmailMessage make_alternative make_mixed make_related for the methods that convert a simple message into a multipart container, email contentmanager EmailMessage add_attachment for the method that attaches a file or bytes to a multipart message, and email contentmanager integration with email.message and email.policy and email.mime and io for building high-level email readers attachment extractors text body accessors HTML readers and policy-aware MIME construction pipelines.

5 min read Feb 12, 2029
AI

Claude Code for email.charset: Python Email Charset Encoding

Control header and body encoding for international email with Python's email.charset module and Claude Code — email charset Charset for the class that wraps a character set name with the encoding rules for header encoding and body encoding describing how to encode text for that charset in email messages, email charset Charset header_encoding for the attribute specifying whether headers using this charset should use QP quoted-printable encoding BASE64 encoding or no encoding, email charset Charset body_encoding for the attribute specifying the Content-Transfer-Encoding to use for message bodies in this charset such as QP or BASE64, email charset Charset output_codec for the attribute giving the Python codec name used to encode the string to bytes for the wire format, email charset Charset input_codec for the attribute giving the Python codec name used to decode incoming bytes to str, email charset Charset get_output_charset for returning the output charset name, email charset Charset header_encode for encoding a header string using the charset's header_encoding method, email charset Charset body_encode for encoding body content using the charset's body_encoding, email charset Charset convert for converting a string from the input_codec to the output_codec, email charset add_charset for registering a new charset with custom encoding rules in the global charset registry, email charset add_alias for adding an alias name that maps to an existing registered charset, email charset add_codec for registering a codec name mapping for use by the charset machinery, and email charset integration with email.message and email.mime and email.policy and email.encoders for building international email senders non-ASCII header encoders Content-Transfer-Encoding selectors charset-aware message constructors and MIME encoding pipelines.

5 min read Feb 11, 2029
AI

Claude Code for email.utils: Python Email Address and Header Utilities

Parse and format RFC 2822 email addresses and dates with Python's email.utils module and Claude Code — email utils parseaddr for splitting a display-name plus angle-bracket address string into a realname and email address tuple, email utils formataddr for combining a realname and address string into a properly quoted RFC 2822 address with angle brackets, email utils getaddresses for parsing a list of raw address header strings each potentially containing multiple comma-separated addresses into a list of realname address tuples, email utils parsedate for parsing an RFC 2822 date string into a nine-tuple compatible with time.mktime, email utils parsedate_tz for parsing an RFC 2822 date string into a ten-tuple that includes the UTC offset timezone in seconds, email utils parsedate_to_datetime for parsing an RFC 2822 date string into an aware datetime object with timezone, email utils formatdate for formatting a POSIX timestamp or the current time as an RFC 2822 date string with optional usegmt and localtime flags, email utils format_datetime for formatting a datetime object as an RFC 2822 date string, email utils make_msgid for generating a globally unique Message-ID string with optional idstring and domain components, email utils decode_rfc2231 for decoding an RFC 2231 encoded parameter value into a tuple of charset language and value, email utils encode_rfc2231 for encoding a string as an RFC 2231 encoded parameter value, email utils collapse_rfc2231_value for collapsing a decoded RFC 2231 tuple to a Unicode string, and email utils integration with email.message and email.headerregistry and datetime and time for building address parsers date formatters message-id generators header extractors and RFC-compliant email construction utilities.

5 min read Feb 10, 2029

Put these ideas into practice

Claude Skills 360 gives you production-ready skills for everything in this article — and 2,350+ more. Start free or go all-in.

Back to Blog

Get 360 skills free