Keeping your zsh config out of Copilot's terminals
How a single env var separates your shell from the agent's
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:
if [[ "$TERM_PROGRAM" != "vscode" ]]; then # load powerlevel10k, aliases, nvm, etc.fiVS 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:
if [[ "$TERM_PROGRAM" != "vscode" || -n "$VSCODE_USER_TERMINAL" ]]; then # load powerlevel10k, aliases, nvm, etc.fiThe 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
| Terminal | TERM_PROGRAM | VSCODE_USER_TERMINAL | Customisations load? |
|---|---|---|---|
| Any terminal outside VS Code | app-specific or unset | unset | Yes — first condition passes |
| VS Code integrated terminal | vscode | 1 | Yes — second condition passes |
| Copilot agent terminal | vscode | unset | No — 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.
Support this blog
If you found this useful, a small tip keeps the writing going.
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