A Dirty Shell
Why the VS Code Integrated Terminal Is Not a Clean Environment
Most developers assume the VS Code integrated terminal is “just a shell inside the editor.” But it isn’t. It looks like your shell, but it behaves like a contaminated, stateful, and editor-controlled environment that reflects:
- VS Code's startup state
- extension overrides
- cached state from previous sessions
- injected environment variables
- PATH rewrites
- pseudo-terminal layering
- lingering references to deleted files/folders
This creates the most common (and frustrating) failure mode:
A fresh external terminal works perfectly, but the VS Code integrated terminal is broken or behaves inconsistently.
We call this phenomenon:
A Dirty Shell
A shell that looks safe but isn't. A shell whose environment comes not from your OS, but from VS Code’s world. A shell that silently loads state you didn’t ask for — and do not know you have.
This article explains:
- How to inspect a dirty shell
- Exactly how VS Code pollutes the terminal
- How environment inheritance differs from a real terminal
- How to compare VS Code’s shell vs a clean system shell
- How to reset the integrated terminal properly
- How to avoid the “dirty shell trap” entirely
1. The Clean Shell vs The Dirty Shell
Clean Shell
Launched by your OS:
Ctrl + Alt + T (Linux)
⌘ + Space → Terminal (macOS)
Win + R → cmd/pwsh (Windows)
It inherits a minimal, predictable environment:
- system PATH
- user-level environment
- login/session vars
- your
.bashrc,.zshrc,.profile, etc.
Nothing else.
Dirty Shell (VS Code)
Launched inside VS Code:
Terminal → New Terminal
It inherits:
- VS Code process environment
- Injected variables from extensions
- Editor IPC handles
- Persisted environment from previous sessions
- Extension hooks into PATH
- VS Code-specific variables
- Potential stale environment values
- Workspace overrides
It is, by default, unintentionally polluted.
2. Why VS Code Creates a Dirty Shell
Reason #1 — VS Code injects environment variables
To coordinate extensions and editor features, VS Code injects variables like:
VSCODE_GIT_IPC_HANDLE
VSCODE_INTEGRATED_TERMINAL
ELECTRON_RUN_AS_NODE
VSCODE_PIPE_LOGGING
VSCODE_IPC_HOOK
These affect:
- git behavior
- node/npm behavior
- shells running in pseudo-TTY mode
- extension communication
None of these exist in a clean system shell.
Reason #2 — VS Code keeps shell sessions alive
Even after you:
- delete directories
- remove virtual environments
- change PATH
- switch projects
- reload the window
the shell remains alive unless manually killed.
This leads to:
- stale PATH values
- references to deleted files
- zombie environment variables
- broken runtime behavior
Reason #3 — Extensions silently rewrite your environment
Examples:
- Python extension modifying PATH or activating envs
- Docker extension injecting DOCKER_* variables
- GitLens injecting IPC hooks
- Remote/SSH extensions rewriting shell startup
- Node/TS extension injecting NODE_OPTIONS
- Go extension injecting GOROOT / GOPATH
These can dramatically alter command behavior.
Reason #4 — VS Code launches the shell from the wrong parent
System shell inheritance chain:
OS login → Session → Shell
VS Code shell inheritance chain:
VS Code (Electron)
→ Extension Host
→ Pseudo Terminal
→ Your Shell
Totally different parent environment = totally different shell behavior.
3. Commands to Compare “Clean Shell vs Dirty Shell”
These commands should be run side by side:
- Open a CLEAN shell:
Ctrl + Alt + T - Open a VS Code terminal: Terminal → New Terminal
Run the following in both.
(A) Compare Parent Process
ps -o ppid= -p $$
ps -o comm= -p $(ps -o ppid= -p $$)
Clean shell example:
gnome-terminal-server
systemd
Dirty shell example:
code
code
(B) Diff the environment
Dump environment from both:
In fresh terminal:
env | sort > ~/env.clean
In VS Code terminal:
env | sort > ~/env.vscode
Then:
diff -y ~/env.clean ~/env.vscode | less
You will see:
VSCODE_*variables- PATH injected by extensions
- Dirty env vars from previous sessions
- Editor IPC hooks
- Node/Electron flags
(C) Compare PATH
printf "%s\n" ${PATH//:/\\n} | nl
Differences often include:
- VS Code helper directories
- extension binary shims
- remote environment overrides
(D) See which files VS Code keeps open
lsof -p $$
You will often see:
- Electron pipes
- VS Code IPC sockets
- Extension temp dirs
- Project files that are deleted but still open
(E) Compare shell startup path
echo $SHELL
echo $0
Many devs are shocked to see:
- same
$SHELL, but - different
$0(because of pseudo-TTY layering)
4. How to Reset the Dirty Shell (Properly)
✔ Kill terminal using the trash-can icon
This is the only real reset.
✔ Start a new terminal
This gives you a fresh pseudo-TTY instance.
✘ DO NOT rely on:
clearresetreload windowcdinto project root- switching tabs
- opening file → run task
None of these fix the environment.
5. How to Avoid the Dirty Shell Trap
1. Always verify with a clean external terminal when something behaves strangely.
If:
- build tools fail
- virtualenv activation breaks
- PATH looks wrong
- interpreter mismatch occurs
Always re-test in a real terminal.
2. Use env diffs to quickly pinpoint pollution
Your env.clean vs env.vscode diff is HIGH-signal.
3. Kill VS Code terminals often
Treat them as temporary. They are not stable long-running shells.
4. Disable auto-activation / helper injections for languages you don’t want polluted
For example:
"python.terminal.activateEnvironment": false
"terminal.integrated.inheritEnv": false
(But this has tradeoffs.)
6. Summary
VS Code’s integrated terminal is not “your shell in a panel.” It is a pseudo-terminal process living inside an Electron application, inheriting state from:
- VS Code itself
- its extensions
- its cached environment
- its workspace logic
- previous sessions
Can we make the integrated shell act exactly like an external one?
Long answer: There are settings that get you closer, and you can disable most (but not all) of VS Code’s environment pollution — but you cannot make it truly identical to a real external terminal because:
- VS Code is still the parent process
- You still run inside a pseudo-terminal
- Extensions can still modify the environment
- VS Code must inject some internal variables to function
- PATH and env inheritance still come from VS Code, not your OS session
BUT — you can get extremely close with the right settings.
Below is the full breakdown.
🚫 No setting can make VS Code terminal identical to a real shell
An external terminal always follows this parent chain:
systemd → login session → your terminal app → shell
VS Code always follows this:
VS Code → Electron → Node/Pseudo-terminal → shell
Because the parent environment is different, you can never fully eliminate:
- editor IPC variables
- environment inheritance
- pseudo-TTY quirks
- extension-provided env vars
- VS Code’s injected variables
So the answer is no, you can’t make it fully external.
But…
✔️ You CAN make it “as clean as possible”
Here are the settings that minimize pollution.
1. Disable inherited environment vars
This forces VS Code to not pass its own environment into the terminal.
"terminal.integrated.inheritEnv": false
This is the single most effective “cleaning” setting.
Downside: Some dev tools (e.g., debuggers) may not work because they rely on IPC variables.
2. Disable shell integration
VS Code injects code into your shell unless you disable this:
"terminal.integrated.shellIntegration.enabled": false
This removes:
- auto-detection snippets
- VS Code’s fancy prompts
- command decorations
- run-after-command hooks
This gets you closer to a real shell.
3. Remove automatic environment activation
If you use Python, Node, Rust, Go, etc., extensions often activate environments automatically.
Disable these:
Python
"python.terminal.activateEnvironment": false
Node
"nodejs.autoDetect": "off"
Go
"go.toolsEnvVars": {}
4. Disable persistence of terminals
VS Code tries to “helpfully” remember terminal state across window reloads:
"terminal.integrated.enablePersistentSessions": false,
"terminal.integrated.persistentSessionReviveProcess": "never"
This stops the terminal from “remembering” stale state.
5. Disable extension environment modifications
For example, GitLens injects environment variables. Disable:
"gitlens.advanced.terminalLinks.enabled": false
Docker extension:
"docker.helper": false
Remote extension:
"remote.autoForwardPorts": false
Each extension is different, but most have a “don’t touch terminal env” setting.
6. Set your shell explicitly
Example (Linux):
"terminal.integrated.defaultProfile.linux": "bash"
7. Disable VS Code’s internal terminal environment variables (partial)
There is no official toggle for all internal vars, but disabling logging removes several of them:
"terminal.integrated.enableFileLinks": false,
"terminal.integrated.confirmOnKill": false,
"terminal.integrated.allowAutomationShell": false
Some VSCODE_* variables cannot be removed (VS Code won’t function without them).
8. Turn off environment variable collections
Newer versions of VS Code allow extensions to “collect” environment variables:
Turn this off:
"security.workspace.trust.enabled": false,
"terminal.integrated.environmentChangesIndicator": "off"
🚀 The closest possible config: A “Clean as Possible” VS Code Terminal
Here is the best-known setup:
{
"terminal.integrated.inheritEnv": false,
"terminal.integrated.shellIntegration.enabled": false,
"terminal.integrated.enablePersistentSessions": false,
"terminal.integrated.persistentSessionReviveProcess": "never",
"python.terminal.activateEnvironment": false,
"terminal.integrated.defaultProfile.linux": "bash",
"gitlens.advanced.terminalLinks.enabled": false,
"docker.helper": false,
"remote.autoForwardPorts": false,
"terminal.integrated.allowAutomationShell": false
}
This gives you:
- no automatic activation
- no VS Code shell injection
- no persistent shell state
- no IPC helpers
- minimal extension contamination
- minimal PATH rewriting
It’s still not a true external terminal — but it’s as close as VS Code can get.
⚡ If you want absolute purity…
There is only one guaranteed 100% clean workflow:
Open an external terminal
Run your commands there
If needed, connect VS Code via Remote/Attach
Or run VS Code from a clean terminal:
env -i bash --noprofile --norc
code .
…but even then VS Code injects some environment back into child shells.