Skip to main content

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:

  1. How to inspect a dirty shell
  2. Exactly how VS Code pollutes the terminal
  3. How environment inheritance differs from a real terminal
  4. How to compare VS Code’s shell vs a clean system shell
  5. How to reset the integrated terminal properly
  6. 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:

  • clear
  • reset
  • reload window
  • cd into 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.