pybreakz is beta software — APIs may change. Report issues on GitHub.
GitHub

Overview

pybreakz is a zero-dependency Python debugger CLI built for coding agents. Set breakpoints on specific line numbers, run your script, and get a clean structured snapshot of local variables and call stack state — all in one shot. No interactive session required.

It's particularly useful for AI coding agents like Claude Code. Instead of adding print() statements or reading large log files (which eat up context window), the agent runs pybreakz and gets a precise, structured report at any line in the code. The output is deliberately compact and token-optimised — only the data you asked for, no noise.

How agents use it: Read the code, identify suspicious lines, run pybreakz run script.py --breakpoints 34,67 --watch "x,response", read the report, patch the bug. Repeat if needed. The --on-exception flag is a good first pass — let the crash tell you where to look.
pybreakz is in beta. Core functionality is stable but the CLI interface may change between versions. Please report bugs on GitHub.

Installation

Install from PyPI:

pip install pybreakz

Requires Python 3.9 or later. Zero pip dependencies — only the standard library.

Or install from source:

git clone https://github.com/allannapier/pybreaks.git
cd pybreaks
pip install -e .

Verify it's working:

pybreakz --version

Quick Start

Point pybreakz at your script and tell it which line numbers to break on:

pybreakz run script.py --breakpoints 7 --watch "item,len(items)"

pybreakz runs the script and produces a structured report for each hit — compact enough to be token-efficient, detailed enough to debug without guessing:

════════════════════════════════════════════════════════════
  pybreakz report  →  script.py
════════════════════════════════════════════════════════════

┌─ BREAKPOINT HIT #1  script.py:7
│  Source:          result = process(item)
│
│  Call Stack (innermost last):
│  <module>()  [script.py:13]
│  main()  [script.py:11]
│  run_batch()  [script.py:7]
│
│  Locals:
│    items                = list[3 items]: [{'id': 1, 'name': 'foo', 'active': True}, ...]
│    item                 = dict[3 keys]: {'id': 1, 'name': 'foo', 'active': True}
│    result               ! name 'result' is not defined
│  Watched:
│    item                 = dict[3 keys]: {'id': 1, 'name': 'foo', 'active': True}
│    len(items)           = 3
└────────────────────────────────────────────────────────────

┌─ BREAKPOINT HIT #2  script.py:7
│  Source:          result = process(item)
│
│  Locals:
│    items                = list[3 items]: [...]
│    item                 = dict[3 keys]: {'id': 2, 'name': 'bar', 'active': False}
│    result               = 2
│  Watched:
│    item                 = dict[3 keys]: {'id': 2, 'name': 'bar', 'active': False}
│    len(items)           = 3
└────────────────────────────────────────────────────────────

Basic Breakpoints

Use --breakpoints (or -b) with one or more comma-separated line numbers. pybreakz captures all local variables at each hit.

# Single breakpoint
pybreakz run script.py --breakpoints 42

# Multiple breakpoints
pybreakz run script.py --breakpoints 10,25,42
Breakpoints must be on executable lines — not blank lines, comments, or def/class header lines. Use the first line inside the function body instead.

Watch Expressions

Use --watch (or -w) to evaluate specific expressions in the local scope at each breakpoint hit. More targeted than capturing all locals.

pybreakz run script.py --breakpoints 42 --watch "user_id,response.status,len(items)"

Expressions are comma-separated and evaluated in the local scope at the breakpoint. You can access any variable, call methods, use indexing, or write any valid Python expression.

Eval Expressions

Use --eval (or -e) to evaluate expressions that appear in a separate "Evaluated" section of the output. Useful for computed values you want to distinguish from watched variables.

pybreakz run script.py --breakpoints 42 --eval "df.shape,type(result),items[:3]"

You can use --watch and --eval together in the same command.

On Exception

Use --on-exception (or -x) to capture program state at the point of an unhandled exception — locals, call stack, and full traceback.

# Basic exception capture
pybreakz run script.py --on-exception

# With watched expressions at the crash site
pybreakz run script.py --on-exception --watch "self,request,data"
This is a great first-pass debugging strategy. Run with --on-exception, let the crash tell you where to look, then add targeted --breakpoints on subsequent runs.

Cross-file Breakpoints

Set breakpoints in imported modules, not just the entry script. Use the file:line format instead of just a line number.

# Break in an imported module
pybreakz run script.py --breakpoints "utils.py:55"

# Mix file:line and plain line numbers
pybreakz run script.py --breakpoints "utils.py:55,helpers.py:12,42"

# With a condition
pybreakz run script.py --breakpoints "utils.py:55:x>0,helpers.py:12:done"

Use just the filename (not the full path). pybreakz matches against the end of the module's file path.

Variable Watchpoints

Use --trace-vars (or -V) to record a hit every time a named variable changes value — anywhere in any file. Unlike breakpoints, you don't need to know which line the variable is on.

# Track every change to 'counter' and 'result'
pybreakz run script.py --trace-vars "counter,result"

# Combine with breakpoints
pybreakz run script.py --trace-vars "x" --breakpoints 10

# Track object attributes
pybreakz run script.py --trace-vars "self.state"

Useful when you know a variable is getting an unexpected value but don't know where it's being set. Each hit shows the old and new value:

┌─ WATCHPOINT #1  result  changed  [script.py:2]
│  Source:      result = item['id'] * 2
│  result: 2  →  4
│
│  Call Stack (innermost last):
│  main()  [script.py:11]
│  run_batch()  [script.py:7]
│  process()  [script.py:2]
└────────────────────────────────────────────────────────────

┌─ WATCHPOINT #2  result  changed  [script.py:7]
│  Source:          result = process(item)
│  result: 2  →  4
│
│  Call Stack (innermost last):
│  main()  [script.py:11]
│  run_batch()  [script.py:6]
└────────────────────────────────────────────────────────────

Live Mode & Servers

Use --live (or -L) to stream breakpoint hits to stderr in real-time as they happen. This is designed for long-running processes like servers — pybreakz won't wait for the script to finish before showing output.

# Stream hits from a long-running server
pybreakz run server.py --breakpoints "handler.py:55" --live --max-hits 0

# Add a delay between hits (useful for agents reading output)
pybreakz run server.py --breakpoints "handler.py:55" --live --delay 2

# Pause at each hit with an interactive REPL (implies --live)
pybreakz run server.py --breakpoints "handler.py:55:len(results)>0" --break
For AI agents debugging servers: run pybreakz in the background with --live --delay 2, wait for startup, trigger the route, then tail the output. The delay gives the agent time to read each hit before execution continues.

Flags

  • --live / -L — stream hits in real-time. Default timeout becomes 0 (no limit).
  • --break / -B — pause at each hit with an interactive REPL. Implies --live.
  • --delay SECS / -D — pause N seconds after each hit in live mode before continuing execution.

Conditional Breakpoints

Only fire a breakpoint when a condition is true. Append :condition after the line number.

pybreakz run script.py --breakpoints "42:x>100,67:status=='error'"

The condition is evaluated in the local scope at that line. If it evaluates to falsy, the breakpoint is skipped silently. Useful when a breakpoint is inside a loop and you only care about specific iterations.

Passing Arguments to Your Script

Use -- to separate pybreakz flags from your script's own arguments. Everything after -- is forwarded as sys.argv.

pybreakz run script.py --breakpoints 10 -- --input data.csv --verbose

JSON Output

Use --format json to get machine-readable output. Includes all breakpoint hits, locals, watched values, and call stack data.

pybreakz run script.py --breakpoints 42 --format json

Useful if you want to pipe the output into another tool or process it programmatically.

Timeout & Limits

Control how long pybreakz lets a script run and how many breakpoint hits to record.

# 60 second timeout
pybreakz run script.py --breakpoints 10 --timeout 60

# No timeout
pybreakz run script.py --breakpoints 10 --timeout 0

# Limit breakpoint hits recorded
pybreakz run script.py --breakpoints 10 --max-hits 5

Default timeout is 30 seconds. Default max hits is 20. Set --max-hits 0 for unlimited hits.

Options Reference

Flag Short Default Description
--breakpoints LINES -b Breakpoints to set. Formats: LINE, LINE:CONDITION, FILE:LINE, FILE:LINE:CONDITION. Comma-separated.
--trace-vars NAMES -V Comma-separated variable names. Records a hit whenever any of these change value, in any file.
--watch EXPRS -w Comma-separated expressions evaluated at each hit
--eval EXPRS -e Additional expressions shown in a separate output section
--on-exception -x off Capture locals, stack, and traceback on unhandled exception
--live -L off Stream breakpoint hits to stderr in real-time. Designed for servers and long-running processes. Default timeout becomes 0.
--break -B off Pause at each hit with an interactive REPL. Implies --live.
--delay SECS -D Pause N seconds after each hit in live mode before execution continues.
--timeout SECS -t 30 Script timeout in seconds. Set to 0 for no limit. Defaults to 0 in live/break mode.
--format FORMAT -f text Output format: text or json
--max-hits N 20 Max total hits to record across breakpoints and watchpoints. Set to 0 for unlimited.

Limitations

pybreakz is beta software. These are known limitations in the current release.
  • Works on .py scripts run directly. For long-running servers use live mode.
  • Breakpoints must be on executable lines — not blank lines, comments, or def/class headers. Use the first line inside the function body.
  • sys.settrace has a small performance overhead — not suitable for tight performance benchmarks.
  • Multi-threaded scripts: only the main thread is traced — breakpoints in worker threads will not fire.
  • Async/await with asyncio works for standard patterns. Complex event loop customisation is untested.

For issues or feature requests, open an issue on GitHub.