Short answers to what readers ask most about this topic.
01Do environment variables override Claude Code settings files?
Generally yes, which is what makes them right for CI and containers and wrong for a personal preference. It also means a variable you exported for another project months ago still wins today. If a settings file appears to be ignored, check the environment first — the status and diagnostics commands show what the session actually resolved.
02Which Claude Code environment variables should I set in CI?
Lower the retry count so a pipeline fails fast instead of sitting through a long back-off, and raise the request timeout only if your network is slow. Bound Bash commands with the default and maximum timeout variables, and consider raising the Bash output cap for a run where you need a full log.
03What does disabling non-essential traffic actually turn off?
Feature-flag fetching, telemetry and error reporting collection, mobile push notifications for Remote Control, and background requests that are not essential to core functionality. Because some features depend on flag fetching, those stop working too, without an error naming them. Set it for policy reasons, not to reduce noise.
04How do I keep plugins updated but stop Claude Code updating itself?
Set the auto-updater disable variable and the plugin auto-update override together. The first pins your CLI version to whatever your organisation has approved; the second keeps marketplace plugins current. Without the second, disabling the updater also stops plugin updates.
05Can a script tell it is running inside Claude Code?
Yes. One environment variable is set in every subprocess Claude Code spawns, including Bash, PowerShell, tmux, hooks and MCP servers. A second is set only in direct tool spawns and not in stdio MCP servers, so a script can also tell those two contexts apart and behave accordingly.
Claude Code reads dozens of environment variables, and about fifteen are worth knowing. The most useful group tunes timeouts and limits for automation, where the interactive defaults are wrong. The most dangerous group are three switches whose effects reach further than their names suggest.
I once spent a morning working out why a pipeline sat for eleven minutes before failing. The answer was the retry count: the default is ten, which is generous and correct when a person is watching and would rather wait than start over, and completely wrong for a build agent that should have failed in thirty seconds and told someone.
That is the theme of this post. The defaults are chosen for an interactive session on a laptop, and a build agent, a container image, or a multi-tenant host wants different ones. Here is where environment variables sit in the precedence order, the groups worth setting deliberately, the switches that turn more off than you expect, and the two variables your own scripts can read.
Where environment variables sit
Environment variables generally beat settings files, which is what makes them right for CI and wrong for a personal preference you will want to change later. If you find yourself exporting the same variable in every shell, it probably belongs in a settings file instead — and if you find a settings file being ignored, an environment variable is the first thing to check.
One consequence catches people out: a variable set for one project months ago is still set, and still wins. Authentication is the usual casualty, but the same applies to a model pin or a timeout you set once for a slow network and forgot. Check what the running session actually resolved rather than what you believe your configuration says.
Timeouts and limits, for anything automated
This is the group with the best return on attention. The API timeout defaults to ten minutes, which is right for a long thinking turn on a slow link and far too patient for a pipeline. The retry count defaults to ten, which is the variable behind most mysterious multi-minute delays before a failure. And the Bash defaults bound how long a command may run and how much of its output comes back.
# Timeouts and retries — the defaults are tuned for a person
# sitting in front of a terminal, not for a build agent.
API_TIMEOUT_MS=600000 # default 10 minutes
CLAUDE_CODE_MAX_RETRIES=10 # default; LOWER it to fail fast
BASH_DEFAULT_TIMEOUT_MS=120000 # default 2 minutes
BASH_MAX_TIMEOUT_MS=600000 # ceiling the model may request
BASH_MAX_OUTPUT_LENGTH=30000 # default; hard maximum 150000
# Context and compaction
CLAUDE_AUTOCOMPACT_PCT_OVERRIDE=80 # 1-100, default around 80
CLAUDE_CODE_DISABLE_1M_CONTEXT=1
# Background work
CLAUDE_CODE_DISABLE_BACKGROUND_TASKS=1
CLAUDE_AUTO_BACKGROUND_TASKS=1 # auto-background after ~2 min
CLAUDE_ASYNC_AGENT_STALL_TIMEOUT_MS=600000
The output cap is worth raising before you go hunting for a bug. By default Claude reads back 30,000 characters of Bash output, and a truncated test log looks exactly like a test suite that stopped early. The hard maximum is 150,000 — raising it costs tokens on a noisy command, so raise it for the run where you need the whole log rather than leaving it high everywhere.
Authentication and model pinning
Two groups that overlap more than they look. Authentication variables select and supply a credential; model variables decide what an alias resolves to. On a cloud provider both matter, because the aliases resolve to a built-in default that can lag the newest release. The table below is the shape of it rather than the full list.
The four you will actually set:
Variable group
What to know
The API key variable
Overrides your subscription completely and silently. This is the single most common cause of confusing authentication behaviour
The provider switches
One variable per cloud provider selects where inference runs. The provider's own credentials and region settings then apply
The default-model pins
One per alias. On a third-party deployment, pinning is how you control when your team moves to a new model
The base URL variables
Point Claude Code at a gateway. Note that pointing the main one at a non-first-party host turns off MCP tool search by default
The switches that turn things off
Four of these come up constantly in locked-down environments, and one of them is much broader than its name. Auto-updates, telemetry and error reporting each have their own variable and do what they say. The non-essential-traffic switch is the blunt instrument.
# Updates
DISABLE_AUTOUPDATER=1
FORCE_AUTOUPDATE_PLUGINS=1 # keep PLUGIN updates while the
# CLI updater stays off
# Telemetry and reporting
DISABLE_TELEMETRY=1
DISABLE_ERROR_REPORTING=1
# The blunt instrument. Turns off feature-flag fetching, telemetry
# and error reporting, mobile push for Remote Control, and any
# background request that is not essential — which silently
# disables features that DEPEND on flag fetching.
CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1
That last switch turns off feature-flag fetching along with telemetry and push notifications, which means it also disables features that depend on flag fetching — quietly, with no error to tell you which ones. Set it because your security posture requires it, not to reduce noise, and expect a handful of capabilities to be absent afterwards. The same logic applies to the do-not-track family.
Storage, and hosting more than one tenant
Three variables control where state lives, and the second one exists specifically for hosts that embed Claude Code. By default the project folder is derived from the whole working directory path, which is fine on a laptop and wrong when a host wants each session in its own place. Naming it yourself requires setting the config directory too, and the value is read once at startup — so a settings file cannot supply it.
# Where state lives
CLAUDE_CONFIG_DIR=/srv/tenant-a
CLAUDE_CODE_PROJECT_DIR_NAME=work # name the <project> folder
# yourself; IGNORED unless
# CLAUDE_CONFIG_DIR is set,
# 1-64 [A-Za-z0-9_-], read
# once at startup so an env
# block in settings cannot
# set it
CLAUDE_CODE_SKIP_PROMPT_HISTORY=1 # write no transcripts at all
# Detection, for hooks and scripts that need to know:
# CLAUDECODE=1 set in every spawned subprocess
# CLAUDE_CODE_CHILD_SESSION=1 set in direct tool spawns only,
# not in stdio MCP servers
Variables your own scripts can read
Two are set by Claude Code rather than read by it, and they are the reliable way for a hook or a wrapper to know where it is running:
One is set in every subprocess Claude Code spawns — Bash, PowerShell, tmux, hooks, MCP servers — so a script can detect that it was invoked from a session rather than by a person.
The other is set only in direct tool spawns and not in stdio MCP servers, which is the distinction you need when a script should behave differently inside a long-lived server.
Between them you can make a shared script safe to run in both contexts, instead of writing two scripts or guessing from the terminal type.
The ones with side effects
A few variables reach further than their names imply, and each has produced at least one confusing afternoon. The default Haiku model variable is the clearest example: it is read everywhere the small fast model is used, so changing it also changes conversation summarisation and other background work, not only whatever you were aiming at.
One pairing is genuinely useful and non-obvious. Disabling the auto-updater stops Claude Code updating itself, which many organisations require — but it also stops plugin updates. Setting the plugin-update override alongside it keeps your marketplace plugins current while the CLI version stays pinned to whatever your organisation has approved.
Set the timeout and retry variables on any build agent, because the interactive defaults will otherwise turn a fast failure into a slow one. Keep the blunt traffic switch for environments that genuinely require it, and read the list of what it disables first. And whenever behaviour differs between two machines that should be identical, look at the environment before the configuration — it wins, and it is the part nobody has written down.