Claude Code Environment Variables: Where to Set Them, Which Ones Matter, and How to Prove What Is Active

Claude Code environment variables: where to set them, which scope wins, the empty-value rule, a grouped reference and a drill that proves what is active.

Matrix chart of six Claude Code environment variables against four sources: shell export, user settings, project or local settings and managed settings. API_TIMEOUT_MS reads from all four; CLAUDE_CONFIG_DIR, CLAUDE_CODE_ENABLE_TELEMETRY and CLAUDE_CODE_TMPDIR are ignored in project or local settings; CLAUDE_CODE_RESTRICTED and CLAUDE_CODE_EVEN_MORE_DANGEROUSLY_SKIP_PERMISSIONS read only from the shell.Matrix chart of six Claude Code environment variables against four sources: shell export, user settings, project or local settings and managed settings. API_TIMEOUT_MS reads from all four; CLAUDE_CONFIG_DIR, CLAUDE_CODE_ENABLE_TELEMETRY and CLAUDE_CODE_TMPDIR are ignored in project or local settings; CLAUDE_CODE_RESTRICTED and CLAUDE_CODE_EVEN_MORE_DANGEROUSLY_SKIP_PERMISSIONS read only from the shell.
The file you choose decides whether Claude Code reads a variable at all: API_TIMEOUT_MS works from any source, three variables are ignored in project or local settings, and two count only in the launch environment. Original chart based on Anthropic's Claude Code Environment variables reference, read Oct 30, 2026.

Claude Code environment variables are the controls most developers set by pasting a line from a README or a colleague’s dotfiles, then forget about. Months later a session bills a Console API key instead of a subscription, a proxy reaches the terminal but not a background agent, or DISABLE_TELEMETRY=0 does the opposite of what it appears to say. All three failures have the same cause: nobody can say which source supplied the value the running session actually holds.

This guide is for a developer who runs Claude Code every day, in a terminal or the VS Code extension, on macOS, Linux, WSL or Windows, and who wants to be able to answer that question on demand. You will finish with five things:

  1. A filled precedence table that says where a value can come from, who it reaches and what overrides it.
  2. Three complete settings files that give every variable one owner and one scope.
  3. A grouped reference table of variables worth knowing, with a safe default for each and the risk of getting it wrong.
  4. A ten-minute drill that proves what a running session holds and how quickly an edit reaches it.
  5. A troubleshooting table and a rollback you can run under pressure.

Commands, keys and behaviors come from Anthropic’s Environment variables reference and the pages it links. We did not run these steps for this article, so every “expected” line describes what the documentation says should happen, not output we captured. Paths, hosts and values in the examples are illustrative.

Top of Anthropic's Claude Code Docs page titled Environment variables, with the page introduction and the start of the Set environment variables section.
Anthropic's Claude Code Docs page 'Environment variables', captured from code.claude.com. The capture shows the top of the page; the env block rules, precedence notes and full variables table quoted in this guide appear further down. Source: Anthropic. · Original source

What v2.1.316 added, and why the release is only the trigger

Anthropic published Claude Code v2.1.316 on GitHub on October 31, 2026 at 01:01 UTC, the evening of October 30 in New York. Its notes add three variables and fix a fourth:

  • CLAUDE_CODE_DISABLE_ULTRATHINK turns the ultrathink keyword off.
  • CLAUDE_CODE_SUBAGENT_EFFORT sets the effort level subagents run at when nothing else names one, instead of inheriting the session’s.
  • CLAUDE_CODE_DISABLE_LOOPED_CROSS_CERT_DROP keeps system cross-certificates that certify each other in a loop, for servers whose private CA needs one.
  • CLAUDE_CODE_AUTO_MODE_SERVER=1 is no longer ignored in sessions that a host application such as Claude Desktop starts behind a proxy or LLM gateway with experimental betas turned off.

The same release changed server-managed settings for Team and Enterprise logins: settings fetched from Anthropic’s API now apply without an approval prompt. If your organization delivers an env block that way, its values now reach you without a click.

The pattern matters more than any single entry. Variables keep arriving and changing behavior, and a one-line release note never says which file a variable belongs in or how to confirm it took effect. When we read the reference page for this guide, the three new names were not yet in its table, so their exact value formats are known only from the release notes. Everything below is built so that it also works for the next variable you find in a changelog.

GitHub release page for anthropics/claude-code v2.1.316, showing the release header and the beginning of the What's changed notes.
The anthropics/claude-code v2.1.316 release page on GitHub, published 31 Oct 2026 at 01:01 UTC. The three new variables and the CLAUDE_CODE_AUTO_MODE_SERVER fix are listed in the What's changed notes and may fall below this capture. Source: Anthropic on GitHub. · Original source

Before you start: what you need on the machine

The drill and the worked example only prove something if you start from a known state. Check these first.

  • [ ] Claude Code is installed and claude --version prints 2.1.316 or later if you plan to use the three new variables. Older builds ignore names they don’t know.
  • [ ] You have a throwaway git repository that you have opened in Claude Code and trusted. Project env values wait for workspace trust, so an untrusted test repo gives misleading results.
  • [ ] You can edit your shell profile (~/.zshrc, ~/.bashrc, ~/.profile) or, on Windows, your user environment variables.
  • [ ] You can create and edit ~/.claude/settings.json, .claude/settings.json and .claude/settings.local.json. On Windows, ~/.claude means %USERPROFILE%\.claude.
  • [ ] jq is available for reading env blocks from the command line. It is optional; any JSON viewer works.
  • [ ] No other Claude Code session is open in the test repository while you run the drill.

According to Anthropic’s settings documentation, settings files are strict JSON. A // comment or a trailing comma is a syntax error, and Claude Code reports it as a Settings Error at the next start. Keep a second terminal open where you can run claude doctor, which lists the entries Claude Code rejected.

The five places a value can come from

A session can receive a variable from your shell, or from the env key of four kinds of settings file. A shell value lasts only for the terminal session you exported it in. A settings value applies every time claude runs, however it was launched, because Claude Code reads the file directly. That second property is why an env block fixes the familiar “works in my terminal, missing in the editor” problem covered in the VS Code extension setup guide.

When the same name is set in more than one place, two rules decide. First, a settings env entry replaces the value inherited from the shell in most sessions, because Claude Code writes each entry into its process environment. Second, between settings files, env values follow ordinary settings precedence, so a managed entry beats the same name in user or project settings.

Source Where it lives Who it affects Committed to git? When the session reads it What overrides it
Shell export ~/.zshrc, ~/.bashrc, PowerShell $env:, setx or [Environment]::SetEnvironmentVariable You, in sessions launched from that shell No Once, at startup Any settings env entry with the same name, in most sessions
User settings env ~/.claude/settings.json You, in every project on this machine No At start, and again on each save Project, local, --settings and managed
Project settings env .claude/settings.json Everyone who clones the repo, after they trust the folder Yes, deliberately At start, and again on each save Local, --settings and managed
Local settings env .claude/settings.local.json You, in this project only No. Claude Code adds it to your global git excludes when it writes the file; add it to .gitignore yourself if you create it by hand At start, and again on each save --settings and managed
Managed settings env managed-settings.json, an MDM policy or server-managed settings Everyone your organization deploys it to No; an admin owns it On a schedule for MDM and server delivery; restart to be sure Nothing in your own files

Two cases sit outside that table. A --settings JSON passed at launch sits above every file except managed settings, for that one session only. And cloud sessions read neither your user file nor your local file, since both stay on your machine.

Diagram of how a Claude Code environment variable reaches a session: a stack of managed, local, project and user settings feeds the process environment and replaces the shell value on each save; a dashed amber path from the shell export marks it as a startup-only fallback; the process environment flows down to the Bash tool and hooks, which inherit the value even when it is empty.Diagram of how a Claude Code environment variable reaches a session: a stack of managed, local, project and user settings feeds the process environment and replaces the shell value on each save; a dashed amber path from the shell export marks it as a startup-only fallback; the process environment flows down to the Bash tool and hooks, which inherit the value even when it is empty.
Settings env entries replace the shell's value and are reapplied when you save a file. Between files the higher level wins, and Bash tool and hook subprocesses inherit whatever results, empty strings included. Original diagram based on Anthropic's Environment variables and Settings documentation, read Oct 30, 2026.

Some behaviors have both a variable and a settings key, and for those the winner is decided per pair rather than by level. Check this table before you blame precedence:

Behavior What wins Consequence
Session model --model and /model beat ANTHROPIC_MODEL, which beats the model key in any file A model exported in your profile returns on every launch, whatever you saved with /model
Default model for new sessions ANTHROPIC_DEFAULT_MODEL applies only when no flag, variable, settings file or organization default selects a model It is a fallback, not a pin (v2.1.236 or later)
Effort CLAUDE_CODE_EFFORT_LEVEL beats --effort, /effort and the effort settings Set it and /effort stops changing anything
IDE auto-connect CLAUDE_CODE_AUTO_CONNECT_IDE beats the autoConnectIde setting The setting applies only when the variable is unset

The model rows come from Anthropic’s model configuration page, which is the page to check when a session starts on a model you didn’t pick.

Read each variable’s own rules before copying it

The name tells you what a variable controls. Its row in the reference tells you how the value is parsed and which sources Claude Code will accept it from. Five rules cover most surprises.

  1. Values are literal strings. Claude Code copies env values exactly as written and no shell processes them, so ~ and $HOME stay as typed. Write absolute paths, for example "CLAUDE_CONFIG_DIR": "/home/you/.claude-work", and quote numbers.
  2. Most on/off variables accept 1, true, yes or on to turn the behavior on, and 0, false, no or off to turn it off, in any casing.
  3. Five variables only check whether they are set at all: CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC, DISABLE_TELEMETRY, DISABLE_ERROR_REPORTING, CLAUDE_CODE_TMUX_TRUECOLOR and IS_DEMO. For these, 0 turns the behavior on. Turn them off by unsetting them or setting an empty value. FORCE_HYPERLINK has its own rule: it reads a number, so only 0 disables it, and false turns hyperlinks on.
  4. Numeric variables accept 2e3 and 64_000 as well as plain digits, except where a row says plain digits only. Before v2.1.211, those spellings could silently set a much smaller value.
  5. Some variables refuse some sources. One group is ignored in project and local settings. Another works only in the environment that launches Claude Code and is ignored in every settings env block.

Matrix chart of six Claude Code environment variables against four sources: shell export, user settings, project or local settings and managed settings. API_TIMEOUT_MS reads from all four; CLAUDE_CONFIG_DIR, CLAUDE_CODE_ENABLE_TELEMETRY and CLAUDE_CODE_TMPDIR are ignored in project or local settings; CLAUDE_CODE_RESTRICTED and CLAUDE_CODE_EVEN_MORE_DANGEROUSLY_SKIP_PERMISSIONS read only from the shell.Matrix chart of six Claude Code environment variables against four sources: shell export, user settings, project or local settings and managed settings. API_TIMEOUT_MS reads from all four; CLAUDE_CONFIG_DIR, CLAUDE_CODE_ENABLE_TELEMETRY and CLAUDE_CODE_TMPDIR are ignored in project or local settings; CLAUDE_CODE_RESTRICTED and CLAUDE_CODE_EVEN_MORE_DANGEROUSLY_SKIP_PERMISSIONS read only from the shell.
The file you choose decides whether Claude Code reads a variable at all: API_TIMEOUT_MS works from any source, three variables are ignored in project or local settings, and two count only in the launch environment. Original chart based on Anthropic's Claude Code Environment variables reference, read Oct 30, 2026.

Use the row text to choose the file:

If the variable’s row says Put it in Why
Ignored in project and local settings (CLAUDE_CONFIG_DIR, CLAUDE_CODE_TMPDIR, CLAUDE_CODE_ENABLE_TELEMETRY and the OpenTelemetry export variables) User settings, managed settings or your shell A committed file can’t redirect your config directory, temp files or telemetry
Set it in the environment that launches Claude Code (CLAUDE_CODE_RESTRICTED, CLAUDE_CODE_DISABLE_DANGEROUS_RM_TIMEOUT, CLAUDE_CODE_WARN_DISK_FREE_GIB) Your shell or a wrapper script A copy in a settings env block is ignored
Set it in user or managed settings, not as a shell export (CLAUDE_CODE_PROCESS_WRAPPER) User or managed settings The detached background service has to inherit it
Read once per launch (OpenTelemetry monitoring, CLAUDE_CODE_WEBFETCH_CACHE_TTL_MS) Any allowed file, then relaunch Saving the file doesn’t reach a feature that already started
No restriction The narrowest scope that covers everyone who needs it That keeps one owner per variable

Worked example: audit a shell profile and give each variable one home

The scenario is illustrative. A developer on macOS with zsh works in a large repository called payments-api behind a corporate proxy. Their ~/.zshrc has grown over two years: an API key from an old script, a timeout someone recommended, a telemetry line, and a work profile that a dotfiles repo they don’t control sources from ~/.profile. The goal is to move every value into a deliberate home and delete what nobody needs.

  1. List what the shell will hand to claude at startup, with secrets redacted:
# macOS, Linux, WSL: names and values the next claude launch inherits
env | grep -E '^(ANTHROPIC_|CLAUDE_|DISABLE_|DO_NOT_TRACK|IS_DEMO|API_TIMEOUT_MS|BASH_|HTTPS?_PROXY|https?_proxy|NO_PROXY|NODE_EXTRA_CA_CERTS|MAX_THINKING_TOKENS)' \
  | sed -E 's/(KEY|TOKEN)=.*/\1=<redacted>/'

# where each export lives
grep -nE 'export +(ANTHROPIC_|CLAUDE_|DISABLE_|DO_NOT_TRACK|API_TIMEOUT_MS|BASH_|HTTPS?_PROXY|NO_PROXY|NODE_EXTRA_CA_CERTS)' \
  ~/.zshrc ~/.zprofile ~/.bashrc ~/.profile 2>/dev/null

# what the settings files already set (run from the repository root)
for f in ~/.claude/settings.json .claude/settings.json .claude/settings.local.json; do
  [ -f "$f" ] && echo "== $f" && jq '.env // {}' "$f"
done

On Windows PowerShell, list names only, so that no secret lands in your scrollback: Get-ChildItem Env: | Where-Object Name -Match '^(ANTHROPIC_|CLAUDE_|DISABLE_|DO_NOT_TRACK|API_TIMEOUT_MS|BASH_|HTTPS?_PROXY|NO_PROXY|NODE_EXTRA_CA_CERTS)' | Select-Object Name.

  1. Fill one row per variable and decide. This is the completed audit for the example:
Variable Value found Set in What it actually does Decision New home
ANTHROPIC_API_KEY an old Console key ~/.zshrc line 41 Once approved, it is used instead of the subscription; with -p it is always used Remove, then sign in with /login None
CLAUDE_CODE_USE_VERTEX 1 ~/.profile, from a dotfiles repo you don’t control Selects Google Cloud as the provider Neutralize it, because you can’t delete the line User env, as ""
API_TIMEOUT_MS 1200000 ~/.zshrc Gives each request attempt 20 minutes to receive response headers (default 600000) Keep; this is personal User env
HTTPS_PROXY http://proxy.example.com:8080 ~/.zshrc Routes traffic through the corporate proxy Keep User env, so background agents get it too
DISABLE_TELEMETRY 0 ~/.zshrc Presence-only, so this value still opts out The intent was “keep telemetry on”, so remove it None
BASH_DEFAULT_TIMEOUT_MS 300000 ~/.zshrc 5-minute foreground Bash timeout; the repo’s test suite needs it Share with the team Project env
CLAUDE_CODE_EFFORT_LEVEL high ~/.zshrc Overrides /effort in every project Remove; save a level with /effort instead None
CLAUDE_CODE_AUTO_COMPACT_WINDOW 400000 ~/.zshrc Compacts at 400K tokens in every project Limit it to the large repo Local env

The API key row is the expensive one. The authentication page ranks ANTHROPIC_API_KEY above subscription credentials from /login. In interactive mode you approve it once and that choice is remembered. If the key belongs to a disabled or expired organization, you get authentication failures even though your subscription is fine.

  1. Back up the profile, delete the exports you decided to move or remove, and clear them from the current shell:
cp ~/.zshrc ~/.zshrc.pre-claude-env
# edit ~/.zshrc and delete the export lines for:
#   ANTHROPIC_API_KEY  API_TIMEOUT_MS  HTTPS_PROXY  DISABLE_TELEMETRY
#   BASH_DEFAULT_TIMEOUT_MS  CLAUDE_CODE_EFFORT_LEVEL  CLAUDE_CODE_AUTO_COMPACT_WINDOW
unset ANTHROPIC_API_KEY API_TIMEOUT_MS HTTPS_PROXY DISABLE_TELEMETRY \
      BASH_DEFAULT_TIMEOUT_MS CLAUDE_CODE_EFFORT_LEVEL CLAUDE_CODE_AUTO_COMPACT_WINDOW

Then open a new terminal, so that nothing from the old profile lingers.

  1. Write the three files. The user file, ~/.claude/settings.json:
{
  "$schema": "https://json.schemastore.org/claude-code-settings.json",
  "env": {
    "API_TIMEOUT_MS": "1200000",
    "HTTPS_PROXY": "http://proxy.example.com:8080",
    "NO_PROXY": "localhost,127.0.0.1,.example.com",
    "CLAUDE_CODE_USE_VERTEX": ""
  }
}

The project file, .claude/settings.json, committed in payments-api:

{
  "$schema": "https://json.schemastore.org/claude-code-settings.json",
  "env": {
    "BASH_DEFAULT_TIMEOUT_MS": "300000",
    "BASH_MAX_TIMEOUT_MS": "900000"
  }
}

The local file, .claude/settings.local.json, which never leaves this machine:

{
  "env": {
    "CLAUDE_CODE_AUTO_COMPACT_WINDOW": "400000"
  }
}

Why each choice works:

  • The proxy lives in user settings rather than the shell because, according to the network configuration page, background agents run under a per-user supervisor that inherits whichever shell started it first. A shell-only proxy reaches them by luck. The scheme matters too: a proxy URL Claude Code can’t parse, such as one missing http://, stops launch with an error that names the variable.
  • CLAUDE_CODE_USE_VERTEX is set to an empty string because a settings file can set a variable but can’t remove one. Claude Code treats the empty value as unset for provider selection. If you do run on a cloud provider, the Bedrock and Vertex AI setup guide shows the full env block instead.
  • The two Bash timeouts are team values, so they belong in the committed file. The ceiling is the larger of the two values, and a value above the default background limit also changes unattended sessions.
  • The compaction window affects only you, in only this repository, so it goes in the local file. The context window explainer covers when an earlier compaction point is worth it.
  • Every value is a quoted string, because env values are copied verbatim.
  1. Prove the local file stays out of git and the shared file goes in:
git check-ignore -v .claude/settings.local.json   # prints the matching ignore rule; no output means NOT ignored
git ls-files .claude                              # should list .claude/settings.json and nothing personal
# if check-ignore printed nothing:
echo '.claude/settings.local.json' >> .gitignore

Never put ANTHROPIC_API_KEY or any other secret in .claude/settings.json. If a script really needs a key, export it on that one command line instead of in a profile.

Reference: variables worth knowing, grouped by job

The reference page lists far more variables than any one developer needs. These rows cover the ones most likely to change who pays, which model answers, how traffic leaves the machine and what gets reported. Every name and behavior is from Anthropic’s pages or the v2.1.316 notes, and the three new variables are marked.

Variable What it does Safe default Risk if wrong Where to set it
ANTHROPIC_API_KEY API key sent as X-Api-Key; replaces your subscription once approved, always used with -p Unset for subscription users Billing goes to a Console organization without you noticing One command line for scripts; never a project file
ANTHROPIC_AUTH_TOKEN Authorization: Bearer value for gateways Unset Ranks above the API key and /login User env, for gateway users
CLAUDE_CODE_OAUTH_TOKEN Long-lived token from claude setup-token for CI Unset on laptops Read again in every new session until you remove it The CI secret store
CLAUDE_CODE_USE_BEDROCK, CLAUDE_CODE_USE_VERTEX, CLAUDE_CODE_USE_FOUNDRY Select a cloud provider Unset Outranks every Anthropic credential User or managed; "" to neutralize
ANTHROPIC_BASE_URL Routes requests through a proxy, gateway or local server, as in the LM Studio setup Unset Remote Control is disabled for hosts other than api.anthropic.com User env
ANTHROPIC_MODEL Session model; read before the model key Unset; use /model Overrides your saved choice on every launch Shell, for one session
ANTHROPIC_DEFAULT_MODEL Starting model when nothing else selects one Unset Silently ignored when any file sets model User env
CLAUDE_CODE_EFFORT_LEVEL Effort level (low to max, or auto); beats --effort and /effort Unset /effort stops having any effect Shell, for one session
CLAUDE_CODE_SUBAGENT_MODEL Default model for subagents with no other assignment Unset Subagent cost rises or falls without anyone noticing User or project
CLAUDE_CODE_SUBAGENT_EFFORT (new in v2.1.316) Effort for subagents when nothing else names one Unset Value format isn’t on the reference page yet Local, while you test
CLAUDE_CODE_DISABLE_ULTRATHINK (new in v2.1.316) Turns the ultrathink keyword off Unset Teammates lose a one-turn effort raise Project, once everyone is on v2.1.316
HTTPS_PROXY, HTTP_PROXY, NO_PROXY Proxy routing; lowercase forms are read first; SOCKS isn’t supported Unset A missing scheme stops launch User env
NODE_EXTRA_CA_CERTS Adds your corporate CA file Unset A relative or ~ path won’t resolve User env
CLAUDE_CODE_CERT_STORE Trusted CA sources; default bundled,system Default Narrowing it can break TLS-inspecting proxies User env; it has no dedicated settings key
CLAUDE_CODE_DISABLE_LOOPED_CROSS_CERT_DROP (new in v2.1.316) Keeps looped system cross-certificates for a private CA Unset Only needed for servers whose CA requires it User env, after a TLS error points there
DISABLE_TELEMETRY, DO_NOT_TRACK Opt out of telemetry; both also stop feature-flag fetching Your choice DISABLE_TELEMETRY=0 still opts out; some features become unavailable User or managed
CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC Stops updates, telemetry, error reports and release notes Unset Remote Control becomes unavailable Managed, for locked-down machines
DISABLE_AUTOUPDATER, DISABLE_UPDATES Stop background updates, or block all updates Unset You miss fixes, including env fixes like v2.1.316’s Managed
CLAUDE_CODE_SUBPROCESS_ENV_SCRUB Strips credentials from Bash, hook and MCP subprocess environments Unset Removes variables a script needs; leaves GITHUB_TOKEN User or project
CLAUDE_CODE_EVEN_MORE_DANGEROUSLY_SKIP_PERMISSIONS In bypassPermissions mode, runs dangerous rm commands with no prompt (v2.1.311 or later) Unset Removes a destructive-delete safety prompt Launch environment only

The scrub row pairs with sandboxing. The sandbox setup guide covers the other half of keeping credentials away from commands the agent runs.

The last row deserves a closer look because it is circulating. A post on X on October 27 described CLAUDE_CODE_EVEN_MORE_DANGEROUSLY_SKIP_PERMISSIONS=1 as bypassing permission checks, including safeguards around rm -rf ~. The reference row is narrower. It applies in bypassPermissions mode, removes the prompt for dangerous deletes, needs v2.1.311 or later, and is ignored in a settings env block. So it can’t arrive through a committed project file, but a shell profile or wrapper script can carry it. Search those files for it, and decide permission modes as policy rather than per terminal, as the restricted mode fleet policy guide recommends.

To adopt a variable the reference page doesn’t document yet, such as the three v2.1.316 additions:

  1. Confirm claude --version is at least the release that added it.
  2. Set it in .claude/settings.local.json first, so a mistake affects only you.
  3. Treat the general on/off rule ("1" to turn something on) as an assumption until the variable’s row appears, because five documented variables break that rule.
  4. Find something you can observe. Typed in the terminal prompt, ultrathink shows in rainbow colors when it can raise that turn’s effort (v2.1.311 or later). With CLAUDE_CODE_DISABLE_ULTRATHINK set, we’d expect the keyword to stop doing that, but we haven’t verified it.
  5. For CLAUDE_CODE_SUBAGENT_EFFORT, the model configuration page says CLAUDE_CODE_EFFORT_LEVEL overrides a subagent’s own effort field. The release note doesn’t say how the new variable interacts with it, so don’t set both and expect the subagent value to win. The subagents guide covers the per-agent effort field, which is the documented alternative.
  6. Move it to the project file only after every teammate runs a version that understands it.

The drill: prove what a running session actually uses

/status tells you which files loaded, but not which file supplied each key. To learn which value a session actually holds, ask a subprocess that the session spawned. Claude Code sets CLAUDECODE=1 in Bash tool, hook and status line subprocesses, so a probe that prints it proves the output came from inside the session.

The probe uses a made-up variable called ENV_DRILL, so nothing real changes. Claude Code writes every env entry into the process environment, including names it doesn’t recognize, which is also how teams pass project values such as an Azure DevOps project name to their tools.

# Probe: ask Claude to run this with the Bash tool and show the raw output
echo "CLAUDECODE=${CLAUDECODE:-missing}"; if [ -z "${ENV_DRILL+x}" ]; then echo "ENV_DRILL is unset"; else echo "ENV_DRILL='${ENV_DRILL}'"; fi

Start from a new terminal in the test repository with export ENV_DRILL=shell, then run claude. Work through the steps in order:

Step Action Expected observation What it proves
1 Run the probe CLAUDECODE=1 and ENV_DRILL='shell' Shell exports reach Bash tool subprocesses
2 Add "ENV_DRILL": "user-1" to the user file’s env, save, and probe again without restarting ENV_DRILL='user-1' A settings entry replaces the shell value and applies on save
3 Change it to "user-2", save, probe ENV_DRILL='user-2' Changed values apply live
4 Delete the entry, save, probe Still ENV_DRILL='user-2' A removal doesn’t unset anything until you relaunch
5 Exit and relaunch from the same shell, probe ENV_DRILL='shell' With no settings entry, the shell value applies again
6 Set "user" in the user file and "project" in .claude/settings.json, probe ENV_DRILL='project' Project beats user
7 Add "local" to .claude/settings.local.json, probe ENV_DRILL='local' Local beats project
8 Change the local value to "", probe ENV_DRILL='': set, but empty An empty value is still passed to subprocesses
9 Run /status The Setting sources line lists User, Project and Project local settings Which files loaded, but not which key came from where
10 Remove every ENV_DRILL entry, run unset ENV_DRILL, relaunch, probe ENV_DRILL is unset Clean state again

If step 6 or 7 shows an unexpected value, relaunch and probe again before concluding that precedence is broken. The documentation also notes that some sessions keep the inherited shell value instead, and that a few variables are special-cased, so check the env setting’s exceptions before filing a bug.

Three more checks complete the picture:

  • Credentials. Run /status. When a login and an API key are both configured, it marks the credential that isn’t in use. The “Use custom API key” toggle in /config appears only while ANTHROPIC_API_KEY is set, which is itself a quick test.
  • Model and effort. /model and /effort status show what applies. Effort level: auto means no effort level is sent.
  • Network. Start claude --debug and read ~/.claude/debug/<session-id>.txt for lines such as CA certs: Appended extra certificates from NODE_EXTRA_CA_CERTS (...). In /status, the Proxy row shows the active proxy URL and marks a value it couldn’t parse as invalid and ignored.

On Windows, read one result with care. GitHub issue #20112, opened in January 2026 and closed as a duplicate, reported that env values from .claude/settings.json weren’t visible to the Bash tool. Treat that as a reported issue, not documented behavior. The report tested with echo %AZURE_DEVOPS_PROJECT%, and a bash shell prints %NAME% literally whether or not the variable is set, so repeat the test with the probe above (or $env:ENV_DRILL in the PowerShell tool) before concluding anything. If the value really is missing, set it in the launch environment with setx or [Environment]::SetEnvironmentVariable("ENV_DRILL", "value", "User") and open a new terminal.

Clear a variable on purpose: the empty-value rule

Clearing a variable is where most copied snippets go wrong, because “clear” means different things in different places.

  1. In your shell, unset NAME (or Remove-Item Env:NAME in PowerShell) removes it from that terminal. Delete the export line from the profile too, or it comes back in the next terminal.
  2. In a settings file, deleting the entry stops the file from setting the variable, but it doesn’t remove a value inherited from the shell, and a running session keeps the old value until you relaunch.
  3. To override a value you can’t unset, such as a stale CLAUDE_CODE_USE_VERTEX from a profile you don’t control, set it to "" in a settings env block. Claude Code treats the empty value as unset for provider selection.
  4. Remember that subprocesses still receive the empty string. A tool that checks only whether a variable exists, rather than whether it has a value, will still see it as set.
  5. For the presence-only variables, both deleting the variable and setting it to an empty value turn the behavior off. "0" doesn’t.
  6. For CLAUDE_CODE_EFFORT_LEVEL, auto or unset tells Claude Code to send no effort level at all, which is different from deleting the variable.

Troubleshooting: symptom, cause, fix, expected observation

Symptom Likely cause Fix Expected observation after the fix
A value you exported is ignored A settings env entry with the same name replaced it Remove the entry, or make the file the single owner The probe shows the value you intended
A teammate gets your personal value It was put in .claude/settings.json Move it to .claude/settings.local.json and commit the removal A fresh clone’s project file no longer contains it
An edit didn’t reach the session It was a shell variable, a removal, or a feature that reads once per launch Relaunch claude The probe or feature reflects the new value
Usage is billed to an API key instead of your plan ANTHROPIC_API_KEY exported and approved earlier Unset it, remove the export, relaunch, run /status /status shows your subscription login, with the key not in use or gone
The session starts on a provider you didn’t choose A stale CLAUDE_CODE_USE_* export Set it to "" in user settings /status shows the intended login
Telemetry is off although you set 0 DISABLE_TELEMETRY is presence-only Delete the variable Feature-flag features return in later sessions
Launch stops with an error naming the proxy variable The proxy URL has no scheme Use http://host:port Launch proceeds; /status shows a Proxy row
A path value doesn’t work ~ or $HOME isn’t expanded Write the absolute path The feature finds the file
A project value works for you but not for a new teammate They haven’t trusted the folder yet, or the variable is ignored in project settings Have them trust the folder, or move the variable to user settings Their probe shows the value
The Windows Bash tool can’t see a settings value Wrong probe syntax, or the reported issue Re-test with $NAME; if it is still missing, set it in the launch environment The probe shows the value
A background agent ignores the proxy The supervisor inherited a different shell Put the network variables in user or managed settings New background sessions use the proxy
A value you set never applies A managed settings entry overrides it Check the managed source on /status, then ask your admin Nothing in your own files changes it, which is expected
Settings Error at startup A JSON comment or trailing comma Fix the file; run claude doctor The session starts without the dialog

Claude Desktop sessions where the app manages the provider connection are a special case. There, proxy and TLS variables are read only from managed settings and ~/.claude/settings.json, so a value in a repository’s own settings file won’t apply.

Roll back a change

When a variable change makes things worse, undo it in the order it was applied, then verify.

  1. Restore the shell profile from the backup (cp ~/.zshrc.pre-claude-env ~/.zshrc), or delete the export you added, and open a new terminal.
  2. Remove the entry from the settings file that owns it and save. The audit table tells you which file that is.
  3. Exit and relaunch claude, because removals take effect only at the next launch.
  4. Run the probe for that variable, then /status for login, model and setting sources, and claude doctor to confirm that no file has errors.
  5. For a committed project change, run git revert <sha> so teammates get the previous file on their next pull, instead of deleting the line locally.
  6. If the value comes from managed settings, stop there and contact the administrator. Nothing in your own files can roll it back.

Done means: success and failure checks

You are finished when every box below is true for your machine and your repository:

  • [ ] Every variable in the audit has exactly one owner and one scope, and no name appears in two files with different intent.
  • [ ] /status shows the login method and model you expect, with no unexpected API key in use.
  • [ ] A fresh clone of the repository contains only the shared .claude/settings.json values, and git check-ignore -v .claude/settings.local.json prints a rule.
  • [ ] The drill’s final probe prints ENV_DRILL is unset.
  • [ ] claude doctor reports no rejected settings entries.

Treat these as failures, even if everything seems to work:

  • [ ] A key or token appears anywhere in .claude/settings.json or its git history.
  • [ ] A variable you use doesn’t appear in Anthropic’s reference or a release note. Copied blog snippets with undocumented names do nothing at best.
  • [ ] A launch-only variable such as CLAUDE_CODE_EVEN_MORE_DANGEROUSLY_SKIP_PERMISSIONS sits in a profile nobody reviewed.

Sources

YOU'RE THROUGH THIS ONE.

Keep connecting the dots.

Back to the library

Many assistants.
One place to remember.

Bring your AI sessions, context, and usage together with Automater.

Meet Automater