Five places can set the same key. Four of them are files you write, and one belongs to your organisation. When two disagree, the winner is fixed:
managed > command line > project local > shared project > user.
That fixed order is precedence, the thing module 3 told you instruction files do not have. Here it decides everything.
Managed settings come from managed-settings.json, from an MDM deployment (Mobile Device Management, the software an employer runs to configure machines it owns), or from server-managed policy on the claude.ai console, and nothing you set overrides them. Not --settings, not a local file, nothing. Below that, --settings and the dedicated flags such as --model win for one session. Then .claude/settings.local.json, which is yours and this project’s. Then .claude/settings.json, which is committed and shared with everyone who starts Claude Code in that folder. Then ~/.claude/settings.json, which follows you across every project on the machine.
Installing Claude Code creates none of these. ~/.claude/settings.json appears the first time you change a /config option, and .claude/settings.local.json appears the first time you answer “don’t ask again” to a permission prompt.
Lists merge, values replace
The precedence order above decides single values. A theme in project local beats a theme in user settings, and the user value is gone.
Lists behave differently. permissions.allow in your user file and permissions.allow in the shared project file both apply. Claude Code adds the two lists together instead of picking one, which is what it means to say the rules are unioned. So a teammate’s committed allow rule adds to yours instead of erasing it, which is why a permission you never granted can still be in force in a repo you cloned.
Three keys opt out of merging. fallbackModel is an ordered chain, so the whole value comes from the highest file that sets it. modelPicker takes its whole value from the highest of managed, --settings, or user, and is ignored entirely in project and local files. availableModels from managed settings is applied as-is.
Environment variables, the named values your shell hands to every program it starts, are not a level in this stack. Each one is decided against a specific key. ANTHROPIC_MODEL beats the model key from any file; ANTHROPIC_DEFAULT_MODEL applies only when no file sets model at all.
Strict JSON, and the schema line
Settings files are strict JSON. A // comment or a trailing comma is a syntax error, reported as a Settings Error the next time Claude Code starts, and the whole file is rejected. This costs people an hour more often than any other mistake in this module, because the file looks fine and the failure arrives later.
One line prevents most of it:
{
"$schema": "https://json.schemastore.org/claude-code-settings.json",
"theme": "custom:oxide"
}
That line points at a schema, a published description of which keys are legal and what type of value each one takes. Your editor reads it, autocompletes key names, and flags unknown ones. The schema can lag behind the CLI, the claude command line program itself, so treat a warning on a very new key as a question rather than a verdict.
Most edits take effect while the session keeps running, with no restart. That is what hot-reload means, and permissions, hooks, and apiKeyHelper are among the keys that do it, firing the ConfigChange hook as they land. Three keys are read once at session start and ignore later edits: model, effortLevel, and outputStyle, which you meet in the next module. When you want to know what actually loaded, /status has a Setting sources line, and claude doctor reports entries that were rejected.
The shape on disk
.claude/settings.json ships to your plugin Committed. Everyone who starts Claude Code in this folder gets it.
{
"$schema": "https://json.schemastore.org/claude-code-settings.json",
"permissions": {
"allow": ["Bash(npm test:*)"]
}
} Read more
Read only from the folder you start in, so a committed file in the repo root is invisible if you launch from a subdirectory. Start at the repo root to pick it up.
.claude/settings.local.json Yours, this project only. Written for you on the first "don't ask again".
{
"permissions": {
"allow": ["Bash(docker compose up:*)"]
}
} Read more
Since v2.1.211 this is read and written at the repository root rather than the directory you started in. Claude Code also adds **/.claude/settings.local.json to your global git excludes the first time it writes one, so it stays out of the repo without you touching .gitignore.
home/.claude/themes/oxide.json ships to your plugin The filename minus .json is the slug. Rename the file and you rename the theme.
{
"name": "Oxide",
"base": "dark",
"overrides": {
"claude": "#c96442",
"success": "#3f8f5f",
"error": "#c0392b",
"diffAdded": "ansi256(22)",
"promptBorder": "rgb(80, 80, 92)"
}
} Read more
All three fields are optional. A file containing {} is a valid theme: a dark clone named after the file. Add overrides one token at a time and watch each one land, because the folder is watched and reloaded on save.
home/.claude/settings.json ships to your plugin You, every project on this machine. The theme lives here.
{
"$schema": "https://json.schemastore.org/claude-code-settings.json",
"theme": "custom:oxide"
} Read more
The theme value custom:oxide names a file, not a colour. The slug after the colon is the filename in ~/.claude/themes/ with .json removed.
home/.claude.json Claude Code writes this one for itself. Not a settings file.
{
"projects": {
"/home/you/app": { "hasTrustDialogAccepted": true }
},
"diffTool": "terminal"
} Read more
Sign-in session, MCP server configs, per-project trust decisions, and a handful of global config keys such as diffTool and autoConnectIde. Keys that belong in settings.json are ignored here, and keys that belong here are ignored in settings.json. Neither case produces an error message.
Themes
A theme is one JSON file at ~/.claude/themes/<slug>.json. The filename minus .json is the slug, which is the short lowercase name a thing is looked up by: oxide.json gives the slug oxide. Select it and Claude Code stores theme: "custom:<slug>".
Three fields, all optional:
| Field | Type | What it does |
|---|---|---|
name | string | Display label in /theme. Defaults to the filename slug |
base | string | One of dark, light, dark-daltonized, light-daltonized, dark-ansi, light-ansi. Defaults to dark |
overrides | object | Token name to colour value. Anything you leave out falls through to the base |
So {} is a working theme. It clones dark and takes its name from the file.
Colour values come in five formats: #rrggbb, #rgb, rgb(r,g,b), ansi256(n), and ansi:<name> for the sixteen standard ANSI names such as red and cyan, the set of colours every terminal defines for itself. The ANSI forms follow whatever palette the terminal is configured with, which is the point of the dark-ansi and light-ansi bases.
Tokens
A token is one named piece of the interface you can paint: error colours error text, promptBorder colours the box you type into. The documented list is grouped by what each group paints. A useful subset to start from:
| Group | Tokens |
|---|---|
| Text and accent | claude, text, inverseText, inactive, subtle, suggestion, permission, remember |
| Status | success, error, warning, merged |
| Input box and modes | promptBorder, planMode, autoAccept, bashBorder, ide, fastMode |
| Diff | diffAdded, diffRemoved, diffAddedDimmed, diffRemovedDimmed, diffAddedWord, diffRemovedWord |
| Usage meter and labels | rate_limit_fill, rate_limit_empty, briefLabelYou, briefLabelClaude |
| Shimmer pairs | claudeShimmer, warningShimmer, permissionShimmer, promptBorderShimmer, inactiveShimmer, fastModeShimmer |
Two families are generated rather than listed. Subagent colours are <color>_FOR_SUBAGENTS_ONLY for each of red, blue, green, yellow, purple, orange, pink, cyan, matching the color: frontmatter you will set on an agent in module 8. The ultrathink rainbow is rainbow_<color> and rainbow_<color>_shimmer across red, orange, yellow, green, blue, indigo, violet. Backgrounds have their own group: userMessageBackground, userMessageBackgroundHover, bashMessageBackgroundColor, memoryBackgroundColor, and selectionBg, of which the hover and selection pair apply only in fullscreen mode.
The published list is not the whole set. The terminal configuration page says the /theme editor exposes the same tokens plus “a few single-purpose accents such as onboarding screen colors that are omitted here”, where “here” is that page. It gives no reason for the omission. If you want the authoritative enumeration, open the editor.
Why a typo here is harmless
Unknown token names are ignored. Invalid colour values are ignored. Write promptBoarder or #ff55 and that line does nothing at all; everything else in the file still applies, and the interface still renders. There is no published JSON schema for a theme file, so this tolerance is the only validation you get.
That is why this module teaches precedence on themes rather than on permissions. You can be wrong here in every way available to you and the worst outcome is a colour that did not change.
Claude Code watches ~/.claude/themes/ and hot-reloads on add or change, so you can keep the file open beside the terminal and see each token land. One caveat: if the folder did not exist when the session started, restart once after creating your first file.
/theme, or /config then Theme, lists the built-ins alongside your files. New custom theme… at the end of the list creates one interactively, and Ctrl+E on a highlighted custom theme opens it for editing.
Plugin themes live at <plugin>/themes/*.json with the same schema, and select as custom:<plugin-name>:<slug>. They are marked an experimental component and they are read-only: Ctrl+E on one copies it into ~/.claude/themes/ and edits the copy. Claude Code does not control your terminal’s own colour scheme, only what it draws inside it.
What /config writes where
/config opens Settings, and its Config tab is a short list of personal options rather than every key. Where a change lands is not uniform, which matters for the next section:
/config row | Key | File it is written to |
|---|---|---|
| Theme | theme | ~/.claude/settings.json |
| Editor mode | editorMode | ~/.claude/settings.json |
| Verbose output | verbose | ~/.claude/settings.json |
| Show tips | spinnerTipsEnabled | .claude/settings.local.json |
| Diff tool | diffTool | ~/.claude.json |
| Auto-connect to IDE | autoConnectIde | ~/.claude.json |
You can set a value directly: /config theme=dark, /config verbose=true. One limit worth knowing, since it looks like a bug the first time. key=value cannot turn on a setting that needs panel confirmation, such as autoContinueAtUsageLimit, which lets Claude carry on working with nobody at the keyboard. It can only turn one off. /config --help lists the accepted keys.
statusLine, permissions, hooks, skillOverrides and most of the roughly 350-key reference have no /config row at all. Those you write by hand.
Create ~/.claude/themes/ if it does not exist, then write <your-slug>.json with base and three or four overrides. Start with claude, success, and error, because you will see all three within a minute of using them. Run /theme and select it, then confirm ~/.claude/settings.json now contains theme: "custom:<your-slug>" and add the $schema line while you are in there.
Now prove the reload. Leave the theme file open, change one hex value, save, and watch the interface change without a restart. Then deliberately misspell a token name, save again, and confirm nothing breaks.
Last, prove precedence. Put a different theme value in .claude/settings.local.json in a project and start Claude Code there. Local beats user, so your custom theme loses. Remove it again.
The theme file ships to themes/ in your plugin. The settings.json you wrote ships too, and you will find out in module 12 which of its keys a plugin is allowed to carry.
You open ~/.claude.json, because it is the file with your name on it that you have actually seen before, and you add a permissions block. Or a hooks block. You restart. Nothing happens. No error, no warning, no rejected-entry line in claude doctor. The block sits there looking correct.
~/.claude.json is not a settings file. Claude Code writes it for itself: your sign-in session, MCP server configs, per-project trust decisions, and a small set of global config keys. permissions, hooks, and env belong in ~/.claude/settings.json, one directory further in. The docs name this specific confusion as a config-debugging trap, and it travels with a sibling: keys that do belong in ~/.claude.json, such as diffTool, are ignored if you put them in settings.json. Both directions fail silently.
There is one real overlap, and it is a compatibility path rather than a feature. A theme value left in ~/.claude.json by an older version still applies when no settings file sets one. That is the exception that makes the rule confusing, so name it and move on: if you are configuring behaviour, the file has settings in its name.
/status and its Setting sources line will tell you which files loaded. Check there before you change anything twice.
The idea travels; this particular file does not. Every harness surveyed has some version of managed, then user, then project, then local, with project checked into git and user staying private. The paths differ everywhere. The reasoning about which layer a given key belongs in does not.
One concrete carry: Copilot CLI reads project .claude/settings.json unmodified, added in CLI 0.0.421. Its changelog for 1.0.35 and 1.0.36 records the removal of ~/.claude/ loading for agents, skills, and commands, and most writing about this online predates that reversal. Project-level travels; user-level does not.
Which points at a rule worth adopting now. Config kept in ~/ does not move with the repository, to another harness or to another machine. Anything you want a teammate or another tool to see belongs in the project, and anything private belongs in .claude/settings.local.json.
Themes are the exception with no story at all. The portability research found no theme equivalent in any of the ten harnesses surveyed, and no shared token vocabulary to translate into. Your theme file is the least portable artifact in this course, which is a fair trade for being the safest one to get wrong.
Check yourself
- Your user settings say
"theme": "custom:oxide". A repo you cloned has"theme": "light"in its committed.claude/settings.json. What colour is your terminal? - Your user settings allow
Bash(npm test:*). The project’s shared settings allowBash(git push:*). Which of the two can Claude run without asking? - You add a trailing comma to
.claude/settings.local.json. When do you find out, and what happens to the rest of the file? - Your theme file has
"promptBoarder": "#333". What does Claude Code do with that line?