settings.json: User-level / Project-level Configuration
📚 Series Navigation: The previous post 30 How to Choose Features: CLAUDE.md vs Skill vs Hook vs MCP vs Subagent taught you "which extension point to attach when a requirement comes". This post digs a layer deeper—the switches behind these extension points, which file exactly do they go into, and which overrides which between user-level and project-level.
settings.jsonis the main switchboard for Claude Code, and today we will clear up its wiring rules once and for all.
There is a particularly easy trap to fall into here, and once you step in it, it's hard to forget.
When I first started seriously using Claude Code, a common operation was to stuff a line defaultMode: "auto" into a project's .claude/settings.json, wanting it to automatically allow things and stop asking every time I entered the project. After modifying it, there was no response. The first reaction was that I misspelled the field name, so I checked it against the official documentation word for word three times, and not a single letter was wrong. Then I suspected the JSON format was broken, so I ran it through an online validator, and it was perfectly valid. After tossing around for about twenty minutes, I even started to suspect if this version of Claude Code had a bug.
Later, I saw that sentence in the corner of the documentation: when defaultMode is set to "auto", it will be directly ignored if written in project settings—this is deliberately blocked by the officials to prevent a repository from secretly turning on auto mode for itself (this was also mentioned in post 20). That line of configuration had perfect syntax, the file wasn't broken, it was purely written on the wrong "floor". Moving it to the user-level ~/.claude/settings.json, it took effect in seconds.
Telling this trap is to let you remember one thing: ninety percent of the traps in settings.json are not in "how to write", but in "which layer to write it in, and which layer overrides which". Today, we will break down this set of "floor rules" into pieces, so that you will know what to expect the next time you configure it—whether this rule should be put in the home directory or in the project, you will know at a glance.
After reading this post, you will get:
- Explain in one sentence what
settings.jsonis and how it divides labor with CLAUDE.md - The file locations, scope of influence, and what should be put in the three levels (User / Project / Local)
- A priority table of "who overrides who", plus a most counter-intuitive exception (arrays are merged, not overridden)
- What the several configuration items you will change most often (
model,permissions,env,hooks,statusLine) do respectively, and which layer to put them in - A hands-on practice that can be followed and gives expected output: write a configuration → use
/statusto verify it really took effect
01 Understand First: What is settings.json, and How Does It Divide Labor with CLAUDE.md
Conclusion first: settings.json is Claude Code's "behavior switch assembly"—it manages tool behaviors like permissions, environment variables, default model, Hook, and status line in JSON format; it and CLAUDE.md are two different things, one manages "how to work", the other manages "what to remember".
Many people confuse the two right from the start. You've written CLAUDE.md all the way before (post 18), and configured permission rules (post 20), and next you will configure Hooks—these things ultimately fall into this settings.json file, but it contains completely different content from CLAUDE.md.
Analogy: The project file cabinet in the company vs the breaker box on your cubicle. CLAUDE.md is like the "Project Manual" in the file cabinet—it writes about "we use pnpm not npm", "run tests before committing", this kind of natural language conventions for people (for Claude) to read, and it is read in as background for every conversation. settings.json is different, it is the breaker box on the wall of the cubicle—inside are explicit switches: is this tool allowed to be used, which model to run by default, which script to trigger automatically after modifying a file. The file cabinet is "the rules spoken to it", the breaker box is "the machine behaviors fixed for it".
The officials state its positioning very crisply:
The
settings.jsonfile is the official mechanism for configuring Claude Code via layered settings.
Notice the two words "official mechanism" and "layered", which happen to be the two main threads of this post: it is the formal entry point for configuring Claude Code (not typing parameters temporarily in the command line), and it is stacked in several layers (User-level, Project-level, Local-level).
Down to the real scenarios you will encounter, settings.json manages these types of things:
- "In this project, block commands like
rm -rfdead"—writepermissions.deny - "In this project, using Sonnet by default is fine, don't always use Opus and burn quota"—write
model - "Every time it finishes modifying a file, automatically run a pass of formatting"—write
hooks - "I want the status line at the bottom of the terminal to show the current git branch"—write
statusLine
These are not words "spoken to Claude", but switches that practically change its running behavior. This is the fundamental division of labor between settings.json and CLAUDE.md.
💡 One-sentence summary: CLAUDE.md is "natural language rules spoken to Claude", and
settings.jsonis "the switch assembly that fixes machine behaviors for it"——the former manages what to remember, the latter manages how to work, don't mix up the two sets of things.
02 Three Levels: Home Directory, In Project, or Only on Your Machine
The first thing to understand about settings.json is not what fields there are, but that it has three levels, and the same file name placed in three different locations has vastly different scopes of effect. The trap at the beginning was rooted in not distinguishing the levels.
Analogy: Three places to post notices. The same notice "remember to turn off the air conditioner after work", posted at the company's main gate (visible to every project in the whole company), posted on the door of this office (only visible to people in this project, and registered into the office convention), or posted on a sticky note next to your own monitor (only you see it, others don't know)—the scope is completely different. The three levels of settings.json are these three "posting methods".
The three levels defined officially, look at this table (plus the highest Managed level, dedicated for enterprise IT, which we beginners basically won't touch, and will be mentioned separately in the next section):
| Level | File Location | Affects Who | Into git? | What to Put |
|---|---|---|---|---|
| User | ~/.claude/settings.json | You, across all your projects | No (in your home dir) | Personal preferences: your customary models, themes, tools wanted across projects |
| Project | .claude/settings.json | All collaborators of this repository | Yes (committed to git to share) | Team conventions: permission rules, Hooks, shared MCP |
| Local | .claude/settings.local.json | You, only in this repository | No (automatically gitignored) | Personal overrides, experimental configs with credentials |
For the trade-offs of the three layers, remembering these three sentences is enough:
- "I want it in all my projects" → User-level (
~/.claude/settings.json). For example, "I'm used to using Sonnet by default", "my status line script", configured once, they are there no matter what project you open. - "The whole team should have it, and it must follow the repository" → Project-level (
.claude/settings.json). It is committed into git, teammates get the same set of configs when they pull, this is "configuration as code". - "Only for myself, exclusive to this project, don't want it in version control" → Local-level (
.claude/settings.local.json).
There is a particularly thoughtful detail here, which the officials made clear: when you create .claude/settings.local.json, Claude Code will automatically configure git to ignore it for you.
Claude Code will configure git to ignore
.claude/settings.local.jsonwhen creating it.
Why design it like this? Think about it: the Local level is for putting "personal items"—your personal experimental configurations, things that might carry credentials, these inherently shouldn't be submitted to the version control to pollute teammates. The officials have welded this line of defense dead for you, saving you from accidentally git add . and pushing personal configs up one day. This also echoes the security main thread discussed in post 21: for sensitive things, don't even give them a chance to enter git from the source.
How to Divide in Practice: The Three-Layer Distribution in a Real Project
Just remembering the definitions is not enough, looking at what is put in each of these three layers in a real project will give you an immediate feel:
- User-level (
~/.claude/settings.json): that custom status line script, default model preferences. These have nothing to do with specific projects, they are personal habits "taken wherever you go". - Project-level (
.claude/settings.json): a set ofpermissions.deny(bancurl, ban reading.env), a "automatically run lint before commit" hook. These are the whole team's bottom lines and checkpoints, they must enter git so that every collaborator has them when pulling down. - Local-level (
.claude/settings.local.json): a few commands you temporarily allowed more for yourself (no need for the team to know), a hook that is still experimenting and not mature enough to share with teammates.
To judge which layer a piece of configuration should be placed in, going through a chain of questions in your head is enough: "Is this rule only wanted by myself → User or Local; everyone in the team wants it → Project", and then break it down one step further: "Universal across all projects, and follows me → User; just this project, and don't want it in git → Local".
A mistake in the opposite direction is very common: figuring to save trouble and stacking project-exclusive permission rules all into User-level—as a result, when switching to another project, those rules all followed along, inexplicably bringing out a bunch of inexplicable permissions in an irrelevant project. Only then can you understand: "Should this configuration follow me, or follow the project", is the first judgment of layering. Those that follow the project, honestly put them in the project.
💡 One-sentence summary: Distinguish the three layers in one sentence—cross-all-projects put in User-level
~/.claude/settings.json, whole-team shared put in Project-level.claude/settings.json(into git), private overrides put in Local-level.claude/settings.local.json(automatically gitignored); judgment mantra "follows me vs follows the project".
03 Who Overrides Who: Precedence, Plus a Most Counter-intuitive Exception
All three layers can write the same field. Then the question comes: the User-level says use Opus, the Project-level says use Sonnet, who to listen to? This is what "precedence" has to manage, and it is also the place in settings.json that is most likely to make people dizzy.
First give the official precedence sorting, from highest to lowest (the higher overrides the lower):
| Precedence | Level | In Plain Words |
|---|---|---|
| 1 (Highest) | Managed (Enterprise IT deployment) | Policies locked dead by the company, no one can change |
| 2 | Command-line arguments (--settings, etc.) | Your temporary decision when starting up, only rules for this session |
| 3 | Local-level .claude/settings.local.json | Your private override in this project |
| 4 | Project-level .claude/settings.json | Team shared project settings |
| 5 (Lowest) | User-level ~/.claude/settings.json | Your global default, only takes effect when no one overrides |
This "highest to lowest" suppressive relationship, drawing it into a picture will give you a stronger feel—the higher layer is like a paper covering the lower layer, blocking the parts below that wrote the same field:

This picture from top to bottom is the precedence from high to low: the upper layers override the lower layers (only for single-value fields). In other words, the further up the more "temporary, specific", the further down the more "global, fallback"—only when all the upper layers haven't touched a certain field, it's the turn of the lowest User-level default to take effect.
Remember this order in one sentence: the more "specific to the present", the bigger it is; the more "global fallback", the smaller it is. Command line parameters (just this once) override Local (just this project, just you), Local overrides Project (whole team), Project overrides User (global). The example given by the officials is the most intuitive:
For example, if your User settings allow
Bash(npm run *)but a project's shared settings deny it, the Project settings take precedence and the command is blocked.
That is to say—the green light you gave yourself in the home directory might be vetoed by the project settings when you enter a certain project. This exactly confirms the suspense buried at the end of the previous post: the same configuration, written in your home directory and written in the project, the effect might be exactly the opposite. The trap of defaultMode: "auto" at the beginning essentially stems from not fully digesting this set of layered semantics.
The Managed layer, beginners just need to mention it in one sentence. It is a policy uniformly issued by enterprise IT through MDM, registry, or servers, with the highest priority, which cannot be overridden by User and Project, specifically used for the company to enforce security compliance (for example, "ban curl company-wide"). When you use it alone, or in small team collaboration, you basically won't touch it—knowing that there is such a "ceiling layer" is enough, if you are really in a controlled company environment, then go look up the official server-managed-settings page.
That Most Counter-intuitive Exception: Arrays are "Merged", Not "Overridden"
The "who overrides who" mentioned above targets single values (like model, you write a value, I write a value, the higher layer wins). But there is a category of configurations that completely doesn't follow this, and nine out of ten novices will stumble—array-type configurations (like permissions.allow / deny) are "merged" across layers, not overridden.
What does it mean? Look at the official exact words:
Array settings merge across scopes. When the same array-valued setting appears in multiple scopes, the arrays are concatenated and deduplicated, not replaced.
Translated into plain words: your permission rules will not be "replaced entirely" by project settings, but the rules from both sides will "piece together" and take effect together.
Give an example and you will understand immediately:
| Scenario | Intuition (Wrong) | Actual (Right) |
|---|---|---|
User-level allow: ["Bash(npm run *)"], Project-level allow: ["Bash(git diff *)"] | Project-level priority is high, so only git diff is left | Both take effect: both npm run * and git diff * are allowed |
This is a completely different logic from the single-value field's "high layer overrides low layer", it must be remembered separately:
- Single-value fields (e.g.,
model,defaultMode): High-priority layer covers entirely the lower layer. - Array fields (e.g.,
permissions.allow/deny, multiple variables inenvare similarly assembled): Each layer is pieced together and deduplicated, no one will erase anyone.
This point is very easy to trip people up: thinking that writing a set of deny in the Project-level has replaced that set of loose allow in the User-level, only to find those allows in the User-level are still taking effect—because they are merged, not replaced. Only by understanding this rule will you not "think a certain opening is closed, when in fact it's still wide open from another layer".
💡 One-sentence summary: Precedence mantra "the more specific to the present, the bigger it is" (CLI > Local > Project > User, Managed capped); but array-type configurations (especially permission rules) are merged and deduplicated across layers, not overridden—this is the most counter-intuitive point that is easiest to step on.
04 The Most Frequently Modified Configuration Items: Which Layer to Put Them In, What They Do
The layers and priorities are sorted out, let's look at a few fields that you will actually touch most often. There are hundreds of keys officially supported by settings.json, but 90% of people only touch these few daily. I pick them out, and clearly explain "what they do, which layer is most appropriate to put them in" for each.
First, put a small but complete example to give you an overall impression of what it looks like (this is a simplified version of the official example):
{
"$schema": "https://json.schemastore.org/claude-code-settings.json",
"model": "claude-sonnet-4-6",
"permissions": {
"allow": ["Bash(npm run test *)"],
"deny": ["Bash(curl *)", "Read(./.env)"]
},
"env": {
"CLAUDE_CODE_ENABLE_TELEMETRY": "1"
}
}That $schema line at the beginning is strongly recommended for you to add. It points to the official JSON schema, after adding it, writing configurations in editors like VS Code, Cursor will have auto-completion and real-time validation—if the field name is typed incorrectly or the value type is wrong, the editor will mark it red for you on the spot. The twenty minutes spent checking the field names word for word against the documentation at the beginning, if this $schema line had been added back then, the editor would have prompted it long ago. Official exact words:
Adding this to your
settings.jsonenables autocomplete and inline validation in VS Code, Cursor, and any other editor that supports JSON schema validation.
Below, we tear apart these high-frequency fields one by one:
model: Which Model to Use by Default
model determines which model is run by default in this layer. The value is filled with the model ID (like "claude-sonnet-4-6").
- Which layer to put: Look at your needs. "I personally just love using a certain model" → User-level; "Everyone uniformly uses Sonnet in this project to save quota" → Project-level.
- A point to note:
modelis different from most fields, it is only read once when the session starts, if changed, you must either restart or use/modelto switch in the session. The--modelstartup parameter andANTHROPIC_MODELenvironment variable can temporarily override it (for details on models, see post 5).
This field has a very handy usage: configure different default models for different projects. For example, a documentation-class project, where the work is not heavy, just hardcode model in its Project-level settings.json to use a relatively lightweight model; for another core code project, don't set it, maintaining the stronger default of the User-level. By dividing it this way, it automatically uses the corresponding tier whichever project you enter, no need to manually switch with /model every time—neither wasting the quota of strong models on simple tasks, nor making do on hard-bone projects. This is exactly the practical value of the precedence rule "Project-level overrides User-level": Project-level customizes for this project, User-level acts as fallback for all other projects.
permissions: Is it Allowed to Use a Certain Tool / Command
This was specifically discussed in post 20—allow, ask, deny three types of rules, precisely controlling permissions by tool and by command.
- Which layer to put: Security bottom lines that the team should keep (like "ban
curl", "ban reading.env") → Project-level, enter git so the whole team has it; you personally want to allow a few more commands for convenience → Local-level or User-level. - Remember the rule in section 03:
permissionsis an array, merges across layers—don't expect that writing adenyin one layer can replace theallowin another layer.
env: Inject Environment Variables into the Session
The key-value pairs written in env will be applied as environment variables to every session and child processes run by Claude Code.
Analogy: The badge and equipment uniformly issued before entering the workshop. No matter who comes to work today, upon entering this workshop (session), this set of environment is automatically equipped—env is this set of "standard admission equipment", the variables you declare here will be carried by every command and every child process run in the session.
- Typical usage: Turn on telemetry (
CLAUDE_CODE_ENABLE_TELEMETRY), stuff a fixed variable into a certain toolchain. - Which layer to put: Project-exclusive environment (like a certain service address to connect to in this project) → Project-level; those you want globally → User-level.
hooks: Automatically Run Scripts at Fixed Timings
hooks is the entry point for the "event-triggered automatic actions" repeatedly mentioned in the previous post, and will be specifically dismantled in post 33—it is configured in settings.json. For example, "automatically run formatting after modifying a file", "say hello at the beginning of each session".
- Which layer to put: Checkpoints that the team should run (like "automatically lint before commit") → Project-level; your personal automation habits → User-level.
- How to write exactly, and what events can be listened to, just knowing here that "its home is in
settings.json" is enough, post 33 will expand on it.
statusLine: Custom Bottom Status Bar
Post 14 talked about the "status line" at the bottom of the interface. statusLine lets you customize what it displays—for example, stuff a script in to display the current git branch, current model, and token usage in real time.
{
"statusLine": {
"type": "command",
"command": "~/.claude/statusline.sh"
}
}- Which layer to put: The status bar is a personal visual preference, the vast majority of people put it in User-level (
~/.claude/settings.json), configured once and universal for all projects.
I've compiled the experience of "which layer to put" these common fields into a table, check directly when hesitating:
| Config Item | What it does | My Default Placement |
|---|---|---|
model | Default model | Personal preference → User-level; Project uniform → Project-level |
permissions | Tool / command admission | Security bottom line → Project-level (into git) |
env | Inject env variables | Project exclusive → Project-level; Global → User-level |
hooks | Event-triggered actions | Team checkpoint → Project-level; Personal habit → User-level |
statusLine | Custom status bar | Personal visual → User-level |
An Easy-to-step-on Confusion: Not All Configurations Live in settings.json
Many people don't know this, and often remember it only after being educated by error reports. Claude Code also has another configuration file ~/.claude.json (note, it is .claude.json under the home directory, not the same thing as ~/.claude/settings.json). It contains another type of things: your login session, User/Local scope MCP server configurations (mentioned in post 22 that MCP configs are stored here), trust status of each project, and various caches.
The key trap is—there are a few configuration items that the officials stipulate can only be placed in ~/.claude.json. If you write them into settings.json, it will directly trigger a schema validation error. For example, those named by the officials: autoConnectIde (external terminal automatically connects to IDE), teammateDefaultModel (teammate default model), and the like.
A typical collision scenario is wanting to configure "external terminal automatically connects to VS Code", writing it smoothly into settings.json, and as a result, $schema validation marks it red on the spot, and /status also reports an error. Only by reading the documentation do you know that the home of this field is in ~/.claude.json. So remember: settings.json manages "behavior switches", ~/.claude.json manages "session state / MCP / cache" this kind of behind-the-scenes data—most of the time you only touch the former, but occasionally when a field is "dead set against being written into settings.json", first think about whether it should go to ~/.claude.json.
💡 One-sentence summary: High-frequency fields are just these few—
model(default model),permissions(access control),env(environment variables),hooks(automatic actions),statusLine(status bar); "whole team should have" put in Project-level into git, "personal preference" put in User-level, plus$schemalet the editor check errors for you; note that a few fields (likeautoConnectIde) live in~/.claude.jsoninstead ofsettings.json.
05 Where to Edit, When Do Changes Take Effect, How to Confirm It Really Read It
Now you know how to write fields, there are still three particularly practical problems unresolved: where to edit, whether to restart after modifying, how to know it really took effect. The twenty minutes of tossing around at the beginning, half of the time was consumed on "not sure whether it actually read this configuration or not".
Where to Edit: Directly Modify the File, or Use /config
Two ways:
- Directly use an editor to modify that JSON file—find the file of the corresponding layer according to the position in section 02 and modify it. The CLAUDE.md of this project also recommends "modify file prefer using clear editing", which is more controllable than typing commands temporarily.
- Type
/configin the session—the interactive settings interface provided officially, can see status, modify some common switches (theme, verbose output, etc.).
An easy-to-misunderstand point the officials specifically clarified: that Config tab in /config is not a complete view of your settings.json file content, it is only an editor for a few fixed switches like "theme, verbose output". Don't expect to see every configuration you wrote in /config—for the complete ones, you still have to look in the file.
When Do Changes Take Effect: Mostly Hot-loaded, Two Exceptions Require Restart
This is good news—Claude Code will watch your settings files, and most keys modified will take effect directly in the running session, without restarting. Official exact words:
Claude Code watches your settings files and reloads them when they change... This includes
permissions,hooks, and credential helpers.
But there are two exceptions, they are "only read once upon startup", and need to be restarted (or use the corresponding command to switch) to be recognized after modifying:
| Field | How to take effect after modification |
|---|---|
model | Restart, or use /model to switch in session |
outputStyle | Restart, or rebuild after /clear |
The practical significance of remembering this rule: if you changed permissions or hooks, it takes effect upon saving, just continue using it; but if you changed model and find no change, don't rush to suspect you wrote it wrong—it just requires a restart to be recognized. If the trap at the beginning had occurred on permissions, it could have been verified upon saving, but it happened to hit the exception of "layer being ignored", which caused so much extra detour.
How to Confirm It Really Read It: /status Look at "Setting sources"
This is the most critical trick, specifically curing "I'm not sure which configuration it actually loaded". Type /status in the session, there is a line Setting sources inside, listing exactly which layers of settings are actually loaded in the current session—for example, User settings, Project local settings (the exact label names are subject to the actual interface).
The official explanation for it is very solid:
The
Setting sourcesrow confirms which sources are being read... A layer appears in the list only if at least one key was loaded from that source, so an empty list means no settings sources were found.
This sentence has a lot of information, break it down:
- The layer you wrote appears in the list = this file was successfully read.
- The layer you wrote doesn't appear = Claude Code completely didn't find / didn't read it (mostly the path is placed wrong, like writing
.claude/settings.jsonassettings.json). - If the file has a syntax error (JSON broken, value invalid),
/statuswill directly report an error for you to see, saving you from guessing blindly.
So from now on, after configuring a settings.json, the first thing is to type /status to confirm that layer is really in the list—if I had known this step earlier, the twenty-minute detour at the beginning could have been saved to two minutes.
Configuration Not Taking Effect? Troubleshoot According to This Table
Gather all the "traps" in this post into one troubleshooting table. Next time the configuration you wrote has "no response", don't rush to suspect syntax, self-check in this order, and you will basically find the cause every time:
| Phenomenon | ❌ Don't first suspect | ✅ Check this first |
|---|---|---|
| No response at all after modifying | Field name written wrong? | /status see if your layer is in Setting sources—if not, the path is wrong |
model changed but no effect | Config broken? | model requires restart to take effect (or switch with /model), saving is not enough |
defaultMode: "auto" not working | Spelled wrong? | auto is ignored in Project/Local level, must be in User-level (Section 03) |
Wrote deny but some cmd still runs | deny written wrong? | Permissions merge across layers, most likely another layer has an allow not erased (Section 03) |
| Some field dead set against entering settings.json | JSON format wrong? | It might live in ~/.claude.json (like autoConnectIde, Section 04) |
Looking at this table, there are almost none that truly belong to "syntax written wrong"—the vast majority of "not taking effect" are because concepts like layers, effective timing, and merge semantics are not fully digested. This is exactly the one sentence that the trap at the beginning most wanted to pass on to you: The difficulty with settings.json is never how to write it, it's figuring out its set of layering rules.
💡 One-sentence summary: Modify files or use
/config(the latter only covers a few switches);permissions/hookshot-load upon save,model/outputStylerequire restart; after configuring, be sure to use/statusto look at the "Setting sources" line to confirm your layer was really read; if not taking effect, check layers first, not syntax.
06 Hands-on: Write a User-level + Project-level Configuration, Use /status to Verify It Really Takes Effect
Just reading without practicing is a fake trick. Below I will take you to personally write two layers of configurations, and then use /status to verify they are really loaded—run through the whole chain of "write file → layer → verify" from this post once. Use minimal examples throughout, without relying on your existing complex environment.
Step 1: Create a practice project (in terminal)
mkdir settings-demo && cd settings-demoStep 2: Write a Project-level configuration
Paste the following into settings-demo/.claude/settings.json (allow test commands, block curl dead). The first line $schema lets your editor conveniently do validation:
{
"$schema": "https://json.schemastore.org/claude-code-settings.json",
"permissions": {
"allow": ["Bash(npm run test *)"],
"deny": ["Bash(curl *)"]
}
}Step 3: Write a Local-level configuration
Then paste a private allowance that only belongs to you and does not enter git into settings-demo/.claude/settings.local.json:
{
"permissions": {
"allow": ["Bash(git status *)"]
}
}Expectation: After both files are created, you simultaneously have both "Project-level" and "Local-level" configurations in this project. Note—according to Section 02, settings.local.json will be automatically gitignored (verified in the next step).
Step 4: Confirm Local-level is really ignored by git
git init -q && git status --shortExpectation: In the output, you can see .claude/settings.json (Project-level, to be added to version control), but you cannot see .claude/settings.local.json—it is automatically ignored. Seeing this difference = the rule "Local-level automatically gitignored" from Section 02 is successfully verified by you.
Step 5: Enter a session, use /status to verify both layers are read
claudeAfter entering, type:
/statusExpectation: In the popped-up status info, find the Setting sources line, it should list Project local settings (and perhaps User settings you configured earlier), the exact label names are subject to the actual interface. Seeing the layers you wrote in the list = that configuration was successfully loaded. If a certain layer didn't appear, go back to Section 02 to check if the file path was placed wrong.
Step 6: Use /permissions again to cross-confirm rules took effect
/permissionsExpectation: You can see the newly written rules—npm run test * and git status * are in the allowed list, curl * is in the denied list. Pay special attention: git status * comes from the Local-level, the other two from the Project-level, but they are all taking effect—this is exactly a living example of "permission rules merge across layers, not override" discussed in Section 03.
Running through these six steps, you have personally verified the most core capabilities of settings.json: layered writing, automatically isolating private configurations, using /status to confirm loading, using /permissions to see merge effects. From now on, configuring any layer or any field essentially follows this flow.
💡 One-sentence summary: Getting hands-on is practicing "Project-level + Local-level two configurations →
git statusverifies Local-level is ignored →/statussees both layers are loaded →/permissionssees rule merging"——running through this chain personally, the two most confusing points of layering and merging become clear instantly.
07 Summary
In this post, we have sorted out the "main switchboard" settings.json of Claude Code inside and out—from "what it is" to "how many layers, who overrides who, how to verify after modifying", settling the matter of configuration properly.
Stringing together the core points to review:
| What you want to figure out | Answer | One-sentence Key Point |
|---|---|---|
| Its relationship with CLAUDE.md | Two sets of things | CLAUDE.md manages "what to remember", settings.json manages "how to work" |
| How many layers, where to put | Three layers | User-level ~/.claude/, Project-level .claude/ (into git), Local-level .claude/settings.local.json (auto gitignore) |
| Conflict, who to listen to | More specific is bigger | CLI > Local > Project > User, Managed capped |
| Most counter-intuitive point | Array merging | Arrays like permission rules piece together and deduplicate across layers, not override |
| Which fields are touched most | Five | model/permissions/env/hooks/statusLine |
| How to verify after modifying | /status | Look at the "Setting sources" line to confirm your layer was really read |
You should now be able to: distinguish what settings.json and CLAUDE.md each manage, know whether a configuration should be placed in User-level or Project-level, understand the priority of "who overrides who" and the counter-intuitive exception of "arrays merge, not override", recognize high-frequency fields like model/permissions/env/hooks/statusLine, and know to use /status to confirm it really takes effect after configuring. This set of "layered configuration" capabilities is the wrench you use to adjust Claude Code from "installed and usable" to "fitting you and your team's workflow".
The twenty-minute detour at the beginning boils down to one sentence—didn't distinguish "which layer it was written in". After this post, you can save a big detour: when a configuration doesn't take effect, don't rush to suspect the syntax, first think "did I put it on the wrong floor", and then type a /status to take a look.
Next post 32 "Output Styles"—you just saw a special outputStyle field in settings.json, do you remember it is one of the two exceptions that are "only read once upon startup, require restart to take effect"? The next post specifically talks about it: how to adjust Claude's "speaking style and system prompt", making it switch from the default "working assistant" to an appearance more suitable for explaining, teaching, or other scenarios. Think about it: the same Claude, changing a set of output styles, how far can the style of answers given to you differ?