Keeping your zsh config out of Copilot's terminals

Keeping your zsh config out of Copilot's terminals

How a single env var separates your shell from the agent's

By Roger Rajaratnam 31 March 2026

If you use GitHub Copilot in VS Code and have a heavily configured zsh — Powerlevel10k, a bunch of aliases, nvm or pyenv hooks — you’ve probably noticed the agent making a mess of its terminal output, or occasionally failing because something in your .zshrc conflicts with what it’s trying to run.

The problem is simple: Copilot spins up non-interactive shells to execute commands. Your .zshrc loads anyway, because that’s what .zshrc does. The agent doesn’t need your prompt theme; it just needs to run git status and get on with its life. Your customisations are noise at best, a source of failures at worst.

Suppressing them without also killing them in your regular VS Code terminal is the tricky part.

The naive fix and why it breaks things

The obvious approach is to skip customisations for all VS Code terminals:

Terminal window
if [[ "$TERM_PROGRAM" != "vscode" ]]; then
# load powerlevel10k, aliases, nvm, etc.
fi

VS Code sets TERM_PROGRAM=vscode in every terminal it opens — including the ones you open yourself. So this guard works for the agent, but it also strips your prompt and aliases from your regular integrated terminal. Not what you want.

Why PATH isn’t a reliable signal

The next idea is to detect the Copilot agent via $PATH, since the extension injects its CLI tools into it. The problem: VS Code injects those PATH entries into all terminals — integrated and agent alike. You can’t use it to tell them apart.

The fix: a custom env var

VS Code has a per-platform setting that injects environment variables into terminals the user opens — terminal.integrated.env.osx, terminal.integrated.env.linux, and terminal.integrated.env.windows. Critically, these settings do not apply to terminals that extensions spin up programmatically. This is the clean signal we need.

In VS Code settings.json, add whichever keys match your platform(s):

"terminal.integrated.env.osx": { "VSCODE_USER_TERMINAL": "1" },
"terminal.integrated.env.linux": { "VSCODE_USER_TERMINAL": "1" },
"terminal.integrated.env.windows": { "VSCODE_USER_TERMINAL": "1" }

Then in .zshrc, update the guard:

Terminal window
if [[ "$TERM_PROGRAM" != "vscode" || -n "$VSCODE_USER_TERMINAL" ]]; then
# load powerlevel10k, aliases, nvm, etc.
fi

The logic reads: load customisations if this is not a VS Code terminal, or if it is one that the user opened (as confirmed by the injected var).

Why this works

TerminalTERM_PROGRAMVSCODE_USER_TERMINALCustomisations load?
Any terminal outside VS Codeapp-specific or unsetunsetYes — first condition passes
VS Code integrated terminalvscode1Yes — second condition passes
Copilot agent terminalvscodeunsetNo — both conditions fail

The key is that terminal.integrated.env.* is a VS Code UI concern — it only kicks in when a human is on the other end of the terminal. Agent terminals bypass it entirely, giving you a reliable, low-friction way to distinguish the two situations.

That’s it

The guard is a single if condition and the env var is harmless to everything else in your environment. Add only the platform keys you need — if you only ever work on macOS, one line is enough.

Free · No spam · Unsubscribe any time

Get new posts in your inbox

When the next article drops, I'll send a short note — a link and a summary, nothing else. One email per post.

Did you find this useful?

Comments

Loading comments…

Leave a comment