We all spend a bit of time configuring our shells to make our terminals better and more powerful. A fancy prompt, fnm to switch Node versions when you cd into a project, zoxide so you can jump around by memory instead of typing full paths, atuin for better history. All of it lives in your ~/.zshrc or your shell of choice’s profile. These are great for you or me.
When you start using coding agents — Claude Code, Codex, the lot — I started seeing the tools occationally complain about noise, or have a command fail because it’s using an advanced feature of a tool I replaced. Every extra line my shell printed was tokens I was paying for and noise the model had to read past, and a couple of my “conveniences” were making the agent behave in ways I couldn’t predict; often having to re-run commands.
Your Agent Inherits your Shell Config
Most coding agents run their commands through a non-interactive shell, but a lot of them still source your ~/.zshrc on startup to snapshot your PATH and environment.
So every eval "$(<tool> init zsh)" you’ve got in your rc file runs for the agent too.
Noise Is Tokens
The agent captures the stdout of every command it runs and feeds it back into the model’s context. If your shell prints things, that printing becomes part of the conversation — tokens you’re billed for, and context the model has to read past to find the output it actually asked for.
The worst offender for me was fnm’s --use-on-cd hook. Every time anything runs a cd, fnm helpfully prints Using Node v20.11.0. Great for me, a human, glancing at my terminal. Not great when an agent runs cd src && cat package.json and now has a Using Node... line stapled to the top of the file it just read. 20 or 30 tokens doesnt sound like much, but a coding session can run dozens of commands and accumulate thousands of noisy tokens.
Unpredictable Behavior
Tools like zoxide replaces your cd with a helpful jump to a frequently-used directories. I also had exa installed, which prints a fancy directory listing.
That’s a lovely feature for me. An agent runs cd src expecting to move into ./src, there’s no ./src, and zoxide silently sends it to some remembered src somewhere else on disk. And now the agent has a “bug” and does an investigation into why the cd override is sending it to the wrong place.
Setting up Your Shell
The good news is that the fix is small.
You can determine if your shell should be interactive or not using [[ -o interactive ]] as well as also detecting when you are running a command from an agent based on environment variables like CLAUDECODE, AI_AGENT, CURSOR_AGENT, CODEX_SANDBOX, and OPENCODE.
# Real interactive terminal, or an agent/CI shell?
if [[ -o interactive && -z "$CLAUDECODE$AI_AGENT$CURSOR_AGENT$CODEX_SANDBOX$OPENCODE" ]]; then
INTERACTIVE_SHELL=1
else
INTERACTIVE_SHELL=0
fi
export INTERACTIVE_SHELL
[[ -o interactive ]] is the portable version and catches any agent running a non-interactive shell. However not all agents set the interactive flag on every command, so you should also check for the presence of environment variables from the agents.
Gating the Interactive-Only Stuff
Now that you have the flag, you can wrap the human-only tooling in it. Prompt, history, the cd override, fuzzy finder — none of that helps an agent, and all of it costs tokens or predictability, and also makes your shell lighter on ram and startup time.
if [[ "${INTERACTIVE_SHELL:-1}" == 1 ]]; then
eval "$(starship init zsh)"
eval "$(zoxide init --cmd cd zsh)"
eval "$(atuin init zsh)"
source <(fzf --zsh)
fi
__One item worth calling out: the :-1 default. If INTERACTIVE_SHELL is somehow unset (say this file gets sourced on its own), it defaults to 1 — interactive. __
For tools like fnm that you still want to use with an agent, but have different behavior depending on whether it’s interactive or not, you can use the INTERACTIVE_SHELL flag to conditionally set up the tool.
if command -v fnm >/dev/null 2>&1; then
if [[ "$INTERACTIVE_SHELL" == 1 ]]; then
eval "$(fnm env --use-on-cd --shell zsh)" # human: auto-switch on cd
else
eval "$(fnm env --shell zsh)" # agent: PATH only, no hook
fi
fi
What Not to Do
Do not wrap your PATH and runtime setup in this guard.
It’s tempting to throw everything inside the if block, but your agent still needs the same tools you do. Keep PATH and runtime setup (mise, fnm, cargo, and friends) outside the guard so they run for everyone — otherwise you’ll spend an afternoon debugging why your agent can’t find node. Only gate the things that are purely for your eyes: the prompt, history, cd overrides, and fancy listings.
Let Your Agent Do It
Fittingly, this is a great little task to hand to the very agent you’re trying to make happy. Here’s a prompt you can paste in:
Update my shell profile (
~/.zshrcor equivalent) so it’s leaner when a coding agent runs commands. Agents source the profile but run a non-interactive shell, so any tool that prints on startup or per-cd(fnm’s “Using Node”, directory listings, etc.) wastes tokens and clutters context, andcdoverrides like zoxide can silently send the agent to the wrong directory. Add a flag near the top — set it to interactive only when[[ -o interactive ]]is true AND none of$CLAUDECODE $AI_AGENT $CURSOR_AGENT $CODEX_SANDBOX $OPENCODEare set — and gate interactive-only tooling (prompt, history,cdoverrides, fuzzy finders, fancyls) behind it. KeepPATHand runtime setup ungated so I still get all my tools, and send any extranious messages to stderr. Show me the diff before applying.
This should be quick to apply and won’t break anything you already have set up.