Skip to content
| Marketplace
Sign in
Visual Studio Code>Debuggers>Python: Attach to Running ProcessNew to Visual Studio Code? Get it now.
Python: Attach to Running Process

Python: Attach to Running Process

nunezbenj

|
120 installs
| (1) | Free
Attach the Python debugger to an already-running process from a picker. No launch.json: set breakpoints, run your script anywhere (terminal, SSH), pick it, debug.
Installation
Launch VS Code Quick Open (Ctrl+P), paste the following command, and press enter.
Copied to clipboard
More Info

License

Python: Attach to Running Process

Attach the VS Code Python debugger to a script that is already running — from a picker, with no launch.json.

  1. Set your breakpoints in VS Code.
  2. Run the script from wherever you like: the integrated terminal, a plain SSH session, tmux, a cron job.
  3. Press Ctrl+Alt+A (or click Attach in the status bar), pick the process, done. Your breakpoints are live.

Same effect as adding an attach entry to .vscode/launch.json and launching from the IDE, minus the ceremony.

A manim render started in the terminal, attached from the Attach panel: the breakpoint inside a per-frame updater is hit, locals are inspected, the debugger is disconnected and the render continues

A render started in a terminal. The row's tooltip shows the full command line, cwd and interpreter; one click attaches, the breakpoint inside a per-frame updater fires on the next frame, and after a disconnect the render carries on.

Why

VS Code can already attach to a running Python process (debugpy injects itself by PID), but the workflow buries it: edit launch.json, pick from a raw process list, hope you grabbed the right one. Most people never find out it exists. This extension turns it into one keystroke and a list of your Python processes, showing the script, its arguments, working directory and how long it has been running.

It pairs well with PyCharm-like Evaluate Expression: attach with one, inspect with the other.

Features

  • Attach panel — a plug icon in the activity bar opens a view of your running Python processes; one click on a row attaches. It refreshes itself while visible and shows which processes are already attached.
  • Process picker — lists the current user's Python processes, newest first, with script (relative to its cwd), arguments, PID and age. Editor tooling (debugpy adapters, VS Code Server helpers, language servers) is hidden by default; toggle it with the eye button.
  • No launch.json — the attach configuration is built in memory for the session and never written to disk.
  • Remote-native — the extension runs on the remote host under Remote-SSH, so it sees the processes on the server you're connected to.
  • Preflight with fixes — checks that gdb is installed and kernel.yama.ptrace_scope allows attaching, and shows the one-line fix instead of debugpy's traceback wall.
  • Debug-ready processes — scripts launched with python -m debugpy --listen … are recognized (marked listening) and connected to directly instead of injected. There is also a manual Connect to host:port command and a Copy debug-ready launch command helper for hosts where injection isn't possible.
  • Run with wait — for runs that finish before you could find them in a list (a pytest file, a quick script): start them through the bundled waitattach.py and they pause until the debugger is attached, which the extension does automatically the moment the process starts waiting. No launch.json, nothing added to your code. See Runs that finish too fast.
  • Stop Process — VS Code only offers Disconnect for attached sessions. The stop button on the toolbar and in the panel disconnects and terminates the process, with confirmation and a force-kill fallback.
  • Re-attach guard — warns when you pick a process that was already attached once in this window (see Limitations).

Requirements

  • The Python Debugger extension (installed automatically as a dependency) with a Python interpreter selected.

  • A Linux (or macOS) host for attach-by-PID. On Linux, injection uses gdb and needs ptrace permission:

    sudo apt install -y gdb
    echo 'kernel.yama.ptrace_scope = 0' | sudo tee /etc/sysctl.d/10-ptrace.conf && sudo sysctl --system
    

    Scope 0 still limits ptrace to your own processes (plus root); it does not let users inspect each other's programs. No reboot needed. Run Attach: Check Server Readiness to verify a host.

  • Windows laptops are fine as the client: open your Linux server with Remote-SSH and the extension does its work there. Attaching to processes on the Windows machine itself is not supported.

Usage

Action How
Attach to a running process The plug icon in the activity bar (left), the plug icon next to the Run/Debug button in the editor title bar (Python files), Ctrl+Alt+A, the Attach status-bar item, the plug on the debug toolbar, or Command Palette → Attach: Attach to Running Python Process…
Debug a run that finishes too fast Ctrl+Alt+W / Run with Wait (the open file, pytest on it, the last command again, or a custom command — in a terminal) or Attach: Copy Run-with-Wait Command to paste the same into any terminal — the process pauses until the debugger is attached
Stop a process (like the red Stop button) The stop button on the debug toolbar, or on any row in the Attach panel — disconnects, sends SIGTERM, offers SIGKILL if needed
Refresh / show tooling processes Buttons in the picker's title bar
Connect to a script started with --listen Pick it in the list (marked listening), or Attach: Connect to Listening debugpy (host:port)…
Verify a server Attach: Check Server Readiness (gdb, ptrace)
Troubleshoot Attach: Show Log, Attach: Copy Diagnostic Report, Attach: Open Previous Session Logs

Try it with the bundled sample: run python3 test-python/sleeper.py --tag demo in a terminal, put a breakpoint on the total += n line, press Ctrl+Alt+A and pick sleeper.py --tag demo.

Runs that finish too fast: run with wait

Attaching only works while the process exists. A pytest file or a quick script is gone before you can pick it — and adding a launch.json entry for each of them is exactly the ceremony this extension is meant to remove. So the extension ships a tiny helper, waitattach.py, that runs your program only once a debugger is attached:

Run with wait: the command is copied from the picker, pasted into a terminal, the process shows up as waiting, the debugger attaches by itself and stops on the first line of the scene

The same render, this time through the helper. The picker remembers the last command; the process pauses, shows up as waiting, is attached automatically, and the debugger stops on the first statement — before manim has drawn a single frame.

python ~/.vscode-server/extensions/nunezbenj.python-attach-to-process-1.3.0/resources/waitattach.py -m pytest tests/test_x.py -k align
python …/waitattach.py [-t SECONDS] script.py --your args      # -t 0 = wait forever; default 60 s, then it runs anyway

You never type that path: Attach: Copy Run-with-Wait Command puts the right line on the clipboard, and Ctrl+Alt+W / Run with Wait runs it in a terminal for you. The generated line uses the interpreter selected in VS Code by absolute path, so it runs the same Python in any terminal, activated or not — check the status bar shows the venv you mean. Both offer the same choices: the command you used last time in this workspace (Run again), the open file or pytest on it, and Custom command… for anything else — you type what comes after waitattach.py (-m manim -ql main.py MyScene, -m pytest tests -k name, tools/run.py --flag) and it is remembered, so the next time it is one keystroke and Enter. The command is plain Python with no dependencies, so it works in the integrated terminal, an SSH session, tmux — anywhere on that host.

What happens next:

  1. The helper prints its PID and pauses. While it waits it keeps a marker file in /tmp/waitattach-<uid>/, and the extension watches that directory (inotify, no polling), so the process shows up in the Attach panel instantly, pinned to the top, marked waiting for debugger.
  2. With the default attach.autoAttachWaiting: workspace, the extension attaches on its own when the process runs inside your open workspace (its cwd, script or test file). Otherwise — or with never — you get a notification with an Attach button. always attaches to anything of yours that starts waiting on that host.
  3. As soon as debugpy inside the process reports a connected client, the helper gives your breakpoints a second to land and runs the program with the same sys.argv and sys.path[0] it would have had without the wrapper. The debugger stops on your first breakpoint, even if it is on the first line.

So the whole flow is: set breakpoints, Ctrl+Alt+W (or paste the copied command into your SSH session), and you are stopped in your code.

Two VS Code windows on the same host never inject into the same process: the extension claims the waiting process with an atomic rename before attaching, and the other window backs off. If you keep your own copy of the helper somewhere stable (~/bin/waitattach.py, so aliases survive extension updates — the picker offers to copy the bundled file's path), point attach.waitHelperPath at it; keep the file name, the extension recognizes the process by it.

Try it: open test-python/quick.py, put a breakpoint on the total += i line, press Ctrl+Alt+W, pick Run quick.py. Without the helper that script exits in a blink; with it you are stopped on the first iteration.

The guaranteed path (no gdb, no ptrace)

On a host you don't administer, launch the script debug-ready and connect instead of injecting:

python -m debugpy --listen 5678 --wait-for-client your_script.py --your args

The picker shows it as listening :5678 and connects on selection. This mode also supports disconnecting and reconnecting as often as you like.

Settings

Setting Default Meaning
attach.justMyCode true Set to false to step into library/framework code
attach.processFilter "" Regex on the full command line; only matches are listed
attach.showHiddenProcesses false List editor tooling too
attach.defaultHost / attach.defaultPort localhost / 5678 Defaults for Connect and the copied launch command
attach.pathMappings [] debugpy pathMappings for every attach (not needed under Remote-SSH)
attach.subProcess false Debug child processes spawned after attaching
attach.extraConfig {} Extra keys merged into the generated configuration
attach.debugConsole openOnSessionStart Reveal the Debug Console when an attach starts (neverOpen to leave the panel alone)
attach.showStatusBarItem true Show the status-bar button
attach.debugpyLogToFile false Have debugpy write its own logs (adapter, injector, in-process server)
attach.verboseLogging false Log every process considered, why it was hidden, and the full debug-adapter traffic
attach.autoAttachWaiting workspace What to do when a process started with waitattach.py begins waiting: workspace attaches if it runs inside this window's workspace and asks otherwise, always attaches to any of yours, never always asks
attach.waitTimeout 60 Seconds the generated run-with-wait command lets the process wait before running anyway (0 = forever)
attach.waitHelperPath "" Use your own copy of waitattach.py in generated commands (empty = the bundled one)

Limitations

  • Attach is not time travel. Breakpoints bind when you attach; code that already ran is gone. Great for long-running loops, servers and test runners; for a script that finishes in a second, start it with run with wait (or --listen --wait-for-client).
  • One injection per process. After you disconnect from a process attached by PID, debugpy cannot cleanly attach to it again (a second injection reports success but breakpoints never hit). Restart the script, or use the --listen path, which supports reconnecting. The picker warns you about this.
  • Disconnect can crash the process. debugpy stays inside after a disconnect and tears down its hooks while the program runs; a Python 3.12 process doing heavy native work (a 1080p manim render) has segfaulted a few seconds after disconnecting. If the run matters, leave the session attached until it finishes, or use Stop when you are done with it.
  • Processes belonging to other users are never listed (and could not be attached to anyway).

Troubleshooting & Reporting Bugs

— "Hey, the extension crashed."
— "Run Attach: Copy Diagnostic Report and paste it into an issue — no need to reproduce."
— "But I already reloaded the window…"
— "Doesn't matter — Attach: Open Previous Session Logs, grab the file from the crashed session."

That's the whole workflow. The extension always logs its activity to a file — you do not need to reproduce a problem to report it.

  • Right after something goes wrong: run Attach: Copy Diagnostic Report from the Command Palette (or the … menu of the Attach panel). It copies your environment (versions, interpreter, remote, gdb/ptrace), settings, log location and the recent log in one step, and offers to open a prefilled issue. ⚠️ Review it first: it includes hostnames and the command lines of your processes.
  • After a crash or window reload: logs persist on disk across sessions (last 10 kept). Run Attach: Open Previous Session Logs and pick the file from the crashed session to attach.
  • For deeper traces on a reproducible issue: enable attach.verboseLogging (logs every process considered and the full debug-adapter traffic), reproduce once, then copy the report. For injection failures inside the process, add attach.debugpyLogToFile to get debugpy's own logs.

Issues and feature requests: github.com/nunezbenj/vscode-attach-to-process/issues. Please say whether the process runs locally or on a Remote-SSH host.

Common questions

  • "Did it attach?" — the row in the Attach panel (and the status bar) says attached · N threads once debugpy inside the process has connected; the Debug Console's "Attaching to PID… (elapsed …)" lines stop at that moment. Open Run and Debug → Call Stack to see the threads.
  • "Pause does nothing" — Pause only stops threads that are executing Python code. A thread blocked in a native call (Thread.join, a lock, a socket read without timeout, subprocess.wait) shows up as paused only when it returns to Python. A breakpoint on a line the program keeps hitting is the reliable way in; the Call Stack view still lists the threads either way.
  • "gdb is not installed" / "ptrace_scope is 1" — apply the commands from Requirements, then retry. The messages offer to copy the fix.
  • "did not start" — the Debug Console shows debugpy's own output. A process that exited between listing and attaching, or no Python interpreter selected in the Python extension, are the usual causes.
  • The frozen modules warning debugpy prints is benign for your code.

Development

npm install
npm run compile
npm test                       # unit tests + live /proc discovery
python3 test-python/e2e_attach.py   # full injection round trip via DAP (needs gdb + ptrace)
python3 test-python/e2e_wait.py     # same for run-with-wait: marker, claim, breakpoint on the first line, clean exit

Press F5 in VS Code to run the extension in an Extension Development Host.

License

MIT

  • Contact us
  • Jobs
  • Privacy
  • Manage cookies
  • Terms of use
  • Trademarks
  • Your Privacy Choices
  • Consumer Health Privacy
© 2026 Microsoft