Environment variables in Claude Code set the provider, authentication, model mapping, timeouts, telemetry, feature switches and subprocess behavior. A full official index is placed in the reference at the end of the book; here what matters more are the operating rules - where to set which variable and what it risks. An environment variable is a powerful but blunt tool: it acts globally on the process, so where it is set matters no less than its value.
The place depends on the variable's nature. Authentication for a user or machine is kept in the shell profile or a secret manager; credentials and provider configuration for automation - in CI secrets; non-secret project and user defaults - in the env field of settings.json; a one-off reproducible run is configured with the --settings flag. It helps to gather this into a table once, so as not to put a secret where it will leak into the repository or logs.
| Place | When to use |
|---|---|
| shell/profile or a secret manager | User/machine authentication |
| CI secret/variable | Automation credentials and provider config |
| settings.json -> env | Non-secret project/user defaults |
| --settings | A one-off reproducible run |
Providers carry a separate caution. If you use ANTHROPIC_BASE_URL, Bedrock, Vertex, Foundry or a gateway, document the whole set of provider variables and always check /status: a mixed configuration can quietly send a request to the wrong provider - with different billing and a different policy. Secret values are not placed in a committed settings.json; for them there is a secret store and injection from the environment.
The status line is a configurable status line drawn by your script. The /statusline command helps configure it: the script gets JSON with the session state on stdin and returns one line. The requirements are simple and important: it must be fast, resilient to missing fields and print no secrets. It helps to see the skeleton of such a script once - and remember that a heavy git status on every redraw must not be run in the status line, or the interface will start to lag.
Keybindings let you remap interactive actions but require care. The /keybindings command opens the configuration, and the user file lives in ~/.claude/keybindings.json. The main subtlety is terminal interception: Ctrl and Alt combinations may be intercepted by the shell, IDE or OS before they reach Claude Code. So you start with one remap, check conflicts and keep a path back to defaults rather than rewriting everything at once.
Output styles change the instructions about the form of the answer, not the model's capabilities. Built-in styles and custom Markdown definitions are selected via /config or the outputStyle setting - the standalone /output-style command was removed in version 2.1.91; a style change requires /clear or a restart, because the style goes into the system prompt. A style is appropriate for a durable form - concise, explanatory, domain-specific - but security policy must not be hidden in it: it is harder to inspect, and permissions and the sandbox must still stay deterministic rather than depend on the output style.
It is important to remember that the environment is part of the attack surface. Inherited variables are available to the process, and sometimes to its subprocesses; in an unfamiliar checkout this means someone's configuration can see more than you assumed. Before a run in an unverified repository you use a minimal environment, and for enterprise isolation - credential scrubbing and sandbox credential rules. A variable you forgot about is access you did not notice you granted.
The typical environment and interface failures are predictable. A secret in a committed settings.json instead of a secret store. A mixed provider configuration sending a request to the wrong place without a /status check. A heavy git status in the status line lagging every redraw. A remapped key intercepted by the terminal. And security policy hidden in an output style instead of deterministic permissions. Set variables in the right place, verify the provider via /status and keep security in permissions and the sandbox.
#!/usr/bin/env node
// status line: state JSON on stdin -> one line; fast, no secrets
let input = '';
process.stdin.setEncoding('utf8');
process.stdin.on('data', chunk => (input += chunk));
process.stdin.on('end', () => {
const state = JSON.parse(input || '{}');
const model = state.model?.display_name || state.model?.id || 'model?';
const dir = state.workspace?.current_dir || process.cwd();
process.stdout.write(`${model} · ${dir.split(/[\\/]/).pop()}`);
});