Debug

Xonsh supports two complementary ways to debug xonsh code:

  • Debug in IDE — attach a full graphical debugger from an editor like VS Code.

  • Instant debugging — drop into a debugger at the exact call site with @.debug, with automatic engine selection and a xonsh-syntax REPL when no external debugger is installed.

Debug in IDE

Xonsh extension for VS Code provides syntax highlighting and basic language support for .xsh files. Install via the extensions menu.

Instant debugging

When you need to stop execution at a specific call site without wiring up an IDE, xonsh ships a debugging helper attached to every session as @.debug. It works like Python’s builtin breakpoint(), but with automatic engine selection, session-aware fallbacks, and a xonsh-syntax REPL when no external debugger is installed.

Quick Start

Drop into a debugger at any point in your xonsh code:

@.debug.breakpoint()

The engine is chosen automatically in priority order: pdbpipdbpdbexecereval. The first one that is importable (or, for execer, available on the session) wins.

To force a specific engine for a single call:

@.debug.breakpoint(engine='pdbp')
@.debug.breakpoint(engine='ipdb')
@.debug.breakpoint(engine='pdb')
@.debug.breakpoint(engine='execer')   # xonsh REPL at the call site
@.debug.breakpoint(engine='eval')     # plain-Python REPL

Setting the Default Engine

Set $XONSH_DEBUG_BREAKPOINT_ENGINE in your xonsh RC to change the default used when engine is not passed (or is 'auto'):

$XONSH_DEBUG_BREAKPOINT_ENGINE = 'pdbp'

Allowed values: 'auto' (default), 'pdbp', 'ipdb', 'pdb', 'execer', 'eval'.

An explicit argument to breakpoint() always beats the env var.

Engines

Engine

Description

pdbp

pdbp — enhanced pdb (sticky mode, syntax highlighting, where/u/d frame hiding). Install with xpip install pdbp.

ipdb

ipdb - IPython-flavored pdb. Install with xpip install ipdb.

pdb

Stdlib pdb. Always available.

execer

A REPL at the caller’s frame backed by the session’s Execer. Full xonsh syntax is available — subprocesses (ls, $(ls)), env lookups (@.env['HOME']), aliases, and @. attribute access. Raises RuntimeError if no execer is attached to the session.

eval

A minimal REPL using plain Python eval/exec. Has no dependency on a xonsh session — works in detached contexts (scripts, tests) the same as inside an interactive shell.

When engine='auto' resolves to an engine, @.debug prints a short banner identifying the choice and a one-line hint about how to continue or abort, then drops into that engine.

REPL Commands (execer and eval engines)

Both execer and eval engines start a small REPL at the caller’s frame. The REPL accepts any Python/xonsh expression or statement, and recognizes the following control commands:

Command

Effect

c / cont / continue

Resume execution after the breakpoint.

exit / quit / q

Abort execution — raises xonsh.debug.XonshDebugQuit, which propagates out of the breakpoint() call and unwinds the stack.

EOF / Ctrl-C

Same as continue (least destructive default).

Expression results are printed automatically. Statements (assignments, loops, etc.) run in the caller’s frame, so local variables are visible and modifiable:

execer> @.env['HOME']
'/Users/you'
execer> ls *.py | head -3
debug.py
environ.py
tools.py
execer> my_local_var = 42
execer> c

Routing Python’s builtin breakpoint() Through @.debug

PEP 553 makes Python’s builtin breakpoint() go through sys.breakpointhook. @.debug can install a hook that routes every builtin breakpoint() call through the same engine as @.debug.breakpoint(). Add the following to your xonsh RC:

$XONSH_DEBUG_BREAKPOINT_ENGINE = 'pdbp'
@.debug.replace_builtin_breakpoint()

After this, breakpoint() anywhere in xonsh code — or in any plain Python module loaded inside the session — drops into the configured engine at the call site. To restore Python’s default behavior:

import sys
sys.breakpointhook = sys.__breakpointhook__