Attach-First Debugging
Using VS Code as a UI, Not an Orchestratorβ
Modern editors β VS Code especially β want to βrun your program for you.β They want to:
- select your runtime
- activate your environment
- construct your execution command
- inject extensions
- modify the environment
- control stdout/stderr
- wrap your program in debugging shims
In many cases, this is convenient. But when you care about correctness, reproducibility, or a clean runtime environment, this is the wrong choice.
The simple truth:
**VS Code should not be responsible for launching your program.
VS Code should only attach to it.**
This is the attach-first debugging philosophy:
β Run your program in a clean, trusted, external shellβ
β Let VS Code attach to it purely as a UIβ
β Do not let VS Code orchestrate your runtime environmentβ
You get the best of both worlds:
- full correctness (from clean external shell)
- full debugging UI (from VS Code attach mode)
This approach avoids almost every problem caused by the βdirty shellβ environment inside VS Code.
Why Launch-Mode Debugging Fails You
When VS Code launches your program, it:
- injects environment variables
- inherits VS Codeβs polluted environment
- uses extension-modified PATH
- runs inside a pseudo-terminal parent
- may reuse stale state from previous shells
- may activate the wrong environment/runtime
- may use the wrong interpreter
- may wrap your program in a debug shim incorrectly
You think your program is running in the environment you configured. It is not.
VS Code becomes the source of truth for your runtime, which is almost always the wrong choice.
Why Attach-Mode Debugging Solves This
When you run your program externally and VS Code only attaches:
β Your environment is correctβ
Itβs your OS environment, not VS Code's.
β PATH, variables, aliases, and tools come from your real shellβ
Not from the pseudo-terminal sandbox.
β No extension overrides or VS Code environment injectionβ
All runtime decisions come from the external shell.
β You control how the program is launchedβ
No editor magic, no hidden flags.
β VS Code becomes a passive observerβ
Not an active orchestrator.
Attach-first debugging reverts VS Code to what it should be: a UI, not an execution environment.
The Attach-First Workflow (Universal Pattern)
This is the same across languages:
1. Open a clean external terminal (Ctrl + Alt + T)
2. Start your program with "debug listening" enabled
3. VS Code: Start an "Attach" debugger
VS Code never launches your program. VS Code only attaches to an already-running process.
This guarantees:
- reproducibility
- clean environment
- correct interpreter/runtime
- correct PATH
- correct version managers
- no editor pollution
A Universal Debug Startup Command (Conceptual)
Most languages support:
<runtime> --debug / --inspect / --listen <port> my_program
And VS Code supports:
{
"request": "attach",
"type": "<language-server-type>",
"port": <port>,
"host": "localhost"
}
Once you understand this pattern, everything else is just syntax.
Attach-First Debugging by Language
Python (debugpy)β
External terminal:β
python -m debugpy --listen 5678 --wait-for-client my_script.py
VS Code launch.json:β
{
"name": "Attach to Python",
"type": "python",
"request": "attach",
"connect": { "host": "localhost", "port": 5678 }
}
Node.jsβ
External terminal:β
node --inspect-brk=9229 app.js
VS Code:β
{
"name": "Attach to Node",
"type": "node",
"request": "attach",
"port": 9229
}
Go (Delve)β
External terminal:β
dlv debug --headless --listen=:2345 --api-version=2 .
VS Code:β
{
"name": "Attach to Go",
"type": "go",
"request": "attach",
"mode": "remote",
"port": 2345
}
C/C++ (gdbserver)β
External terminal:β
gdbserver localhost:7777 ./your_binary
VS Code:β
{
"name": "Attach to C++",
"type": "cppdbg",
"request": "attach",
"MIMode": "gdb",
"miDebuggerServerAddress": "localhost:7777"
}
Rust (same as C++ via gdbserver or lldb-server)β
Rust debugging uses the same pattern as C/C++.
Java (JPDA)β
External terminal:β
java -agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=*:5005 Main
VS Code:β
{
"type": "java",
"request": "attach",
"hostName": "localhost",
"port": 5005
}
Why Attach-First Is Objectively Superior
1. Reliabilityβ
Your clean external shell is always correct. VS Codeβs terminal is not.
2. Determinismβ
Attach debugging produces identical behavior to running your program outside of VS Code.
3. Transparencyβ
You see exactly how your program is launched. No hidden editor logic.
4. Portabilityβ
Your debugging setup works with:
- TMUX
- SSH sessions
- Docker containers
- Remote machines
5. Securityβ
You avoid VS Code-inserted environment variables and IPC handles.
6. No βdirty shellβ issuesβ
Attach debugging bypasses VS Codeβs contaminated environment entirely.
Attach-First Debugging Philosophy (One-Sentence Summary)
Always run your program in a clean environment you control. Use VS Code only as a UI lens for debugging β never as the launching authority.
Comments
No comments yet. Be the first!