Python Debug TerminalA Python Debug Terminal for VS Code: open one, and every Python process you launch from it attaches to the debugger automatically — including child processes it spawns. The intention with this extension was to build a Python analogue of the built-in JavaScript Debug Terminal.
UsageOpen a Python Debug Terminal and use Python from it exactly as you normally
would — every process you launch attaches to the debugger on its own, and so do
any child processes it spawns. No launch configuration, no 1. Open a Python Debug Terminal. Open the Command Palette (⇧⌘P)
and run Create Python Debug Terminal, or click the
2. Set a breakpoint and run your script. Set breakpoints the usual way — by
clicking the editor gutter — then run your program from that terminal, e.g.
3. Child processes come free. Anything your script spawns — e.g.
The terminal behaves like any other integrated terminal; the only difference is the injected environment that makes each Python process phone home and attach. See How it works for the mechanism, and Settings to tune wait-for-attach behavior, the skip-list, forked-child handling, and more. How it works
Why
|
| Setting | Default | Meaning |
|---|---|---|
pythonDebugTerminal.waitForClient |
true |
Pause each process at startup until the debugger attaches and sends breakpoints (required for breakpoints to reliably bind). |
pythonDebugTerminal.waitTimeout |
10 |
Max seconds to wait when the above is on. |
pythonDebugTerminal.skipPrograms |
pip, black, ruff, mypy, … | Program names never attached to. |
pythonDebugTerminal.justMyCode |
true |
Passed through to debugpy. |
pythonDebugTerminal.handleForkedChildren |
false |
Debug fork/multiprocessing children via debugpy's patching. |
pythonDebugTerminal.debugLogging |
false |
Verbose bootstrap diagnostics on stderr. |
Develop
npm install
npm run compile # or: npm run watch
Then press F5 ("Run Extension"). In the Extension Development Host,
open the terminal dropdown → Python Debug Terminal (or run the
Create Python Debug Terminal command), then python your_script.py.
Requires the ms-python.debugpy extension (declared as an extension dependency),
which provides the debugpy debug type used to attach.
Tests
The setup follows vscode-js-debug:
mocha + chai + sinon run under tsx
via .mocharc.unit.js, with *.test.ts specs colocated next
to their sources. The Python bootstrap is covered by a stdlib unittest suite.
npm test # fast suite: types + unit (TS) + python
npm run test:types # tsc --noEmit
npm run test:unit # mocha specs: src/**/*.test.ts (run under tsx)
npm run test:py # python3 -m unittest discover -s pydebug -p 'test_*.py'
npm run test:integration # launches a real VS Code instance (see below)
Unit tests (fast, no VS Code)
src/rendezvous.test.ts— the rendezvous wire protocol: token accept/reject, malformed/oversized/portless payloads, dispose, and logger notifications.RendezvousServertakes an injected logger (rather than importing thevscode-backed one) specifically so it unit-tests in plain Node — the same decoupling js-debug relies on.pydebug/test_sitecustomize.py— the attach/skip filtering (debugpy/pydevd self-skip, skip-list by basename and by path component, IPC gating) andsys.pathhygiene. The bootstrap honorsPYDEBUG_DISABLE_AUTOINSTALL=1so it can be imported without attaching.
Integration tests (real extension host)
Mirroring js-debug's test:golden/runTest.js layer,
src/test/runTest.ts uses @vscode/test-electron to
download a real VS Code, install the ms-python.debugpy dependency, open the
fixtures folder as the workspace, and run *.itest.ts
specs (found recursively) inside the extension host:
src/test/extension.itest.ts— the extension activates and registers its command.src/test/terminal.itest.ts—buildTerminalEnvinjects the injector dir onPYTHONPATHplus the IPC/token/wait flags.src/test/breakpoints/— the end-to-end breakpoint suite (see below).
These compile to out/ via tsconfig.integration.json
(kept separate from the esbuild bundle and the tsx unit run). The run launches a
GUI process, so it needs a display: it works locally and on CI (macOS runners, or
Linux under xvfb-run). .github/workflows/ci.yml
runs the fast suite plus the integration tests on Ubuntu and macOS.
Breakpoint E2E (golden snapshots)
src/test/breakpoints/ proves the thing we actually
own: that our injection → rendezvous → attach pipeline delivers the user's
breakpoints to debugpy reliably, including the first-executable-line case that
motivates sitecustomize's wait_for_client. Each
test drives the real user path — set breakpoints through the VS Code UI model,
open a Python Debug Terminal via our command, run a fixture — and observes
debugpy's DAP traffic through a DebugAdapterTracker, then snapshots a curated,
normalized log against a committed .txt golden (js-debug's assertLog()
analog). Coverage: set-before-launch, first line, verified-line reporting,
multiple/conditional/hitCondition hits, logpoints, breakpoint removal, and a
child subprocess that attaches as its own session purely via environment
inheritance. See PLAN.md for the full rationale.
npm run test:integration # assert against the committed goldens
npm run test:golden:reset # RESET_GOLDEN=1 — regenerate goldens, then hand-review the diff
Goldens are sensitive to the debugpy version (ms-python.debugpy 2026.6.0 at
time of writing); on an intentional bump, regenerate with test:golden:reset and
eyeball the diff. Aggressive normalization (paths → ${workspaceFolder}, and a
small allow-list of DAP messages rather than raw dumps) is what keeps that diff
reviewable.
Coverage
Coverage uses nyc (Istanbul) for the
TypeScript side, as in js-debug, and coverage.py
for the Python bootstrap:
npm run coverage # nyc over the tsx unit run -> text + coverage/ (html, lcov.info)
npm run coverage:py # coverage.py over the bootstrap tests (needs: pip install coverage)
nyc maps back to the original .ts via tsx's source maps
(.nycrc.json); coverage.py is configured by
.coveragerc. Both measure the unit-tested code paths, so the
numbers reflect the pure logic (the rendezvous protocol and the bootstrap's
attach/skip filtering) — not the debugpy/GUI paths, which are exercised by the
integration layer instead. CI runs both on every push.
Status
This is a scaffold: the full injection → rendezvous → attach path is implemented
and the Python/PYTHONPATH contracts are verified, but it has not been hardened
for remote/SSH/container path mapping, Windows quoting edge cases, or a published
Marketplace yet release.

