Claude Code Configuration: The Layers, How They Merge, and Where Credentials Belong
Claude Code reads its configuration from more than one place, and the places do not all behave the same way. Most "I changed the setting and nothing happened" reports come from that fact rather than from a broken config file: the value was set, just not in the layer that wins.
This guide covers what the layers are, how they combine, which keys belong in which one, and how to confirm a change actually took effect instead of assuming it did.
Two channels, not one
Configuration reaches the tool through two independent channels.
Environment variables are read from the shell that launches the tool. They are the mechanism behind endpoint swaps — ANTHROPIC_BASE_URL for the address that serves requests, ANTHROPIC_AUTH_TOKEN for the credential that address accepts. Set them and every request goes somewhere else, with nothing else in the client changing. That mechanism, and what survives it, is covered in running Claude Code with alternative models.
Settings files are JSON, named settings.json, and live at several scopes. They carry the things that are awkward to keep re-exporting: permission rules, hooks, the default model, and an env block that pins environment variables so you do not depend on whichever shell happened to launch the session.
The second channel can feed the first. Anything you would otherwise export in a shell profile can be written into the env block of a settings file, which is usually what you want once a setup stops being an experiment.
The scope ladder
Settings files exist at several scopes, and they are read together rather than one instead of another:
- User scope —
~/.claude/settings.json. Applies to every project on the machine. The right home for personal defaults. - Project scope —
.claude/settings.jsoninside the repository. Intended to be committed, so the whole team inherits it. - Project-local scope —
.claude/settings.local.json, alongside the previous one but not committed. This is where anything machine-specific or private belongs. - Managed policy — an administrator-deployed file that sits above all of the above and cannot be overridden from inside a project.
The ordering matters in one direction: the more specific and more local the scope, the later it is applied, and managed policy overrides everything.
They merge per key, not per file
This is the part that surprises people. A more specific file does not replace a less specific one wholesale. The merge happens key by key.
A live example from a working machine: the user-scope file carried permissions, hooks, and a sandbox block; the project-local file next to it carried only permissions. The hooks and sandbox settings from user scope stayed in force for that project — they were not blanked out by a project-local file that simply did not mention them. Only the keys the local file names are the ones it overrides.
The practical consequence is that a small, focused local file is safe. You do not need to copy your whole user configuration into a project just to change one permission rule.
The mirror-image consequence is the one that bites: if the same key is set in two layers, the more local one silently wins, and reading the file you edited tells you nothing about which value is live.
What belongs in each layer
|
Layer |
Good fit |
Bad fit |
|---|---|---|
|
Shell environment |
One-off experiments, a temporary endpoint swap for a single session |
Anything you need reproducibly tomorrow |
|
User |
Personal defaults: model choice, your own permission baseline, an |
Anything a teammate would also need |
|
Project |
Team-wide rules: permission policy for this repo, hooks that enforce a workflow |
Credentials. Always. Without exception |
|
Project |
Your own credential for this project, machine-specific paths, a per-project endpoint override |
Rules the team should share |
|
Managed policy |
Organisation-wide constraints that must not be locally overridable |
Anything that needs per-developer variation |
The single rule worth memorising: a credential belongs in a layer that git does not see. Project scope is committed by design, which makes it exactly the wrong place, and the mistake is easy to make because it is the layer that feels most "correct" for project-specific settings.
The keys you will actually touch
model— the default model for sessions. Useful when a project should run on something other than your personal default.env— a map of environment variables applied to sessions. This is where a base URL and credential go when you want them pinned rather than exported by hand.permissions—allow,deny, andasklists, plusdefaultMode. This is the layer people most often want per project, and it is also the one that merges most cleanly because the lists are additive in spirit.hooks— commands fired at defined points in a session. Because they execute, treat a committed hooks block the way you would treat any other executable in the repository.sandbox— isolation settings, including network allow-lists for outbound access.
MCP servers are a separate system
Model Context Protocol servers are not configured through settings.json. They have their own file — .mcp.json at the project root, intended to be committed so that everyone working on the repository gets the same server list — plus user-level and local scopes that work the same way the settings scopes do.
Keep the distinction in mind when a change does not appear: editing settings.json will never affect which MCP servers load, and the reverse is equally true. Two systems, two files, two places to look.
The same credential rule applies with more force here, because MCP server definitions frequently include tokens for the services they connect to. A committed .mcp.json with a live token inside it is a leaked token.
Pointing the tool at a gateway
For anyone running through a relay or gateway endpoint, the configuration question reduces to which layer rather than which variable. The variables are settled — base URL and auth token, as covered above and in the OpenAI-compatible API primer for the adjacent case where the wire format differs.
The layer choice maps onto how permanent the arrangement is:
- Exported in the shell — for trying an endpoint out. Disappears when the terminal closes, which is a feature while you are still deciding.
- User
settings.jsonenvblock — for a gateway you use by default across everything. - Project
settings.local.json— when one repository should use a different endpoint or a differently scoped credential from the rest of your work. This is also the right shape when a project's credential rotates on its own schedule; see API key rotation practices.
If access to the official endpoint is itself the constraint rather than the configuration, the route comparison in Anthropic Claude API access is the more relevant read.
Verifying that a change took effect
Assume nothing. The failure mode described at the top of this article — an edit in the wrong layer — produces no error message at all, so verification has to be positive rather than the absence of complaint.
Three checks, in increasing order of confidence:
- Confirm the file parses. A settings file with a trailing comma or an unclosed brace is invalid JSON, and an invalid file cannot contribute any keys. This is the cheapest thing to rule out and a surprisingly common cause.
- Confirm the value is live, not just written. Check the effective configuration the session is actually running with, rather than re-reading the file you edited. When two layers set the same key, only this tells you which one won.
- Confirm behaviour changed. For an endpoint swap, the endpoint on the other side should show the traffic. For a permission rule, the action it governs should now be allowed or blocked. Behaviour is the only check that cannot be satisfied by a value that is set but inert.
Frequently asked questions
Why did my setting not take effect?
In order of likelihood: the same key is set in a more local layer that is overriding yours; the file is not valid JSON so none of it applies; you edited settings when the thing you wanted lives in MCP configuration, or the reverse; or the session was started before the edit and is still running with what it read at launch.
Do project settings replace my personal settings?
No. They merge per key. Keys the project file does not mention keep the value from your user-scope file, which is why a project file containing only a permissions block leaves your hooks and other settings intact.
Where should the API credential go?
In a layer git does not track: your user-scope settings file, a project-local settings file, or the shell environment. Never in the committed project settings file, and never in a committed MCP configuration.
Can different projects use different endpoints?
Yes — that is exactly what project-local scope is for. Put the base URL and credential in .claude/settings.local.json inside that repository, and sessions started there use it while everything else keeps your defaults.
Are MCP servers configured in settings.json?
No. They use their own configuration file with its own scopes. Editing one has no effect on the other.
The short version
Configuration arrives through environment variables and settings files; settings files exist at user, project, project-local, and managed scopes; they merge key by key rather than replacing each other; credentials belong only in layers that are not committed; MCP servers are configured separately; and a change is not confirmed until you have checked the effective value rather than the file you edited.
ROIBest AI serves the Messages API shape at an OpenAI-compatible and Anthropic-compatible endpoint, which means the configuration surface described here is the whole integration — a base URL and a credential, placed in whichever layer matches how permanent you want the arrangement to be.