Settings precedence, highest first
the top value wins
- Managed settingsyour organisation; you cannot override it
- Command line
claude --settingsfor this session - Project local
.claude/settings.local.json— you, this project - Shared project
.claude/settings.json— committed, everyone - User
~/.claude/settings.json— you, every project
Four files, four audiences
Claude Code reads settings from four files, plus managed settings an organisation delivers. Each has a scope, and choosing correctly is most of the discipline.
| File | Who it affects | What belongs there |
|---|---|---|
~/.claude/settings.json | You, in every project on this machine | Personal preferences: theme, editor mode, your default model |
.claude/settings.json | Everyone working in that folder | Team permissions, hooks, plugins, project environment |
.claude/settings.local.json | You, in this one project | Personal overrides and experiments before you share them |
| Managed settings | Everyone the organisation deploys to | Security policy and compliance requirements |
Two operational details matter more than they look. The shared project file is only shared if you commit it — until then it is an ordinary file on your disk and nobody else has it. And the local file is meant to stay out of the repository: Claude Code keeps it out of your commits when it creates the file, but if you create it by hand, adding it to .gitignore is your job. The documentation's own example is the clean one: the team's committed file sets a model for everyone, and a developer who wants a different one sets it in their local file, changing only their own sessions.
A few settings never take effect from a repository file at all, and some — project allow rules, extra marketplaces, most environment values — wait until each teammate trusts the folder. Deny rules apply immediately. That asymmetry is deliberate: a committed file may loosen nothing until you trust it, but it may restrict things straight away. The precedence order and the scope rules are covered from the operator's side in 3.1; here they are a configuration-management concern.
CLAUDE.md as configuration
A CLAUDE.md file is the project's standing instructions: conventions, how the repository is laid out, the commands that matter, the things a newcomer always gets wrong. Anthropic's guidance for repository automation is to define code style, review criteria and project rules there, because Claude follows them when working in the repository — and, in the same breath, to keep the file concise, since it is read on every run.
Treat it as configuration rather than as documentation. It is committed, reviewed and diffed like code; it earns its length; and a rule that belongs to one person goes somewhere personal instead. The hierarchy of CLAUDE.md files and how they combine is 3.1's subject.
A CLAUDE.md that earns its tokens
Read on every run, mostly wastedtext
# Our project
This project was started
in 2023 by the platform
team. It is written in
TypeScript. TypeScript is
a typed superset of
JavaScript.
We care about quality
and we value clear
communication.
History: v1 was a
prototype, v2 added
billing ...Specific and actionabletext
# Conventions
- Tests: `pnpm test`
(not npm)
- Migrations live in
db/migrations; never
edit an applied one
- Public API changes
need a changeset
# Gotchas
- `src/legacy/` is
frozen; open a ticket
instead of editingPinning the model
A model identifier is a dependency version, and it belongs in configuration, once, not scattered across call sites. What has changed is what pinning looks like. From the 4.6 generation onward, identifiers are dateless — claude-sonnet-4-6, claude-opus-5 — and each one is itself a fixed snapshot: the documentation states plainly that such an ID is not an alias, it is the snapshot, and that Anthropic does not update the weights of an existing model ID. New versions ship as new identifiers.
Aliases in the old sense — an evergreen pointer that resolves to the newest dated snapshot — exist only for pre-4.6 models, and those are explicitly not recommended for production, because the thing they point at moves. Dated identifiers such as claude-haiku-4-5-20251001 remain pinned snapshots. Every identifier, dated or dateless, carries its own deprecation and retirement schedule, which is the maintenance half of the story (2.2).
// config.ts — the only file that names a model
export const config = {
// Pinned identifier; changing it is a reviewed commit, not a deploy-time guess
modelId: process.env.CLAUDE_MODEL_ID ?? "claude-sonnet-4-6",
promptVersion: "triage/2026-09-12", // travels with the model in logs
schemaVersion: 3,
} as const;
// Every call site reads config.modelId — nothing hard-codes a model name.
const resp = await client.messages.create({
model: config.modelId,
max_tokens: 512,
system: prompts.triage,
messages,
});Versioning prompts
A prompt is the part of the system with the most influence over behaviour and, in most teams, the least version discipline. The remedy is not exotic: prompts live in the repository as files, they change on a branch with a reason in the commit message, and they are reviewed like code. Then the evaluation set can be run against the old and new versions, and “the answers got worse in September” becomes a question with an answer.
The habit that turns this from tidiness into usefulness is recording the combination. A given output was produced by a model identifier, a prompt version, a schema version and a set of tool definitions. Log all four with the result. Change one at a time when you can, so that when quality moves you know which lever moved it.
What has to be pinned together
- Model identifiera snapshot, in configuration
- Prompt versiona file in the repository
- Schema versionthe output contract
- Tools and pluginsdefinitions and their versions
Plugin dependencies
Plugins can depend on other plugins, and by default a dependency tracks the latest available version — which means an upstream release can change what your plugin runs on without anybody deciding to. Version constraints fix that. Dependencies are listed in the dependencies array of .claude-plugin/plugin.json: a bare string for “whatever the marketplace provides”, or an object with name, an optional semver version range such as ~2.1.0, and an optional marketplace.
{
"name": "deploy-kit",
"version": "3.1.0",
"dependencies": [
"audit-logger",
{ "name": "secrets-vault", "version": "~2.1.0" }
]
}Resolution is git-tag based: releases are tagged {plugin-name}--v{version}, and Claude Code fetches the highest tag satisfying the range. When several installed plugins constrain the same dependency, the ranges are intersected and the highest version satisfying all of them wins; ranges that cannot be reconciled fail the install with a range conflict rather than silently picking one. Depending on a plugin in a different marketplace is refused unless the root marketplace opts in by listing that marketplace in allowCrossMarketplaceDependenciesOn, so one marketplace cannot quietly pull in code from a source you never reviewed.
| Error | What it means | What you do |
|---|---|---|
dependency-unsatisfied | A declared dependency is missing or disabled | Install it, or enable it |
range-conflict | Two plugins' ranges cannot be combined | Update or remove one, or ask upstream to widen its range |
dependency-version-unsatisfied | The installed version is outside the declared range | Re-resolve by reinstalling the dependency |
no-matching-tag | No release tag satisfies the range | Check upstream tagging, or relax the range |
Two management conveniences follow from the same mechanism. A plugin whose manifest is little more than a dependencies array is a bundle — installing it installs the whole curated set, which is how a platform team ships “everything a backend engineer needs” as one install. And rolling that bundle out across an organisation is a settings job: add it to enabledPlugins in managed settings. Enabling a plugin also enables what it depends on; disabling one is refused while another enabled plugin still needs it.
Traps the wrong answers are built from
| Tempting but wrong | Do this instead |
|---|---|
| Hard-coding a model identifier at each call site | Name it once in configuration and have every call site read it. |
| Using a pre-4.6 alias in production | Use a pinned snapshot identifier, since aliases move to the newest version under you. |
| Committing personal preferences in the shared project file | Keep them in your user file, or in the project-local file that stays out of git. |
| Declaring plugin dependencies by bare name only | Add a semver range so an upstream release cannot change your plugin's behaviour without a decision. |
| Keeping prompts outside version control | Store them as files, review changes, and log the prompt version with each result. |
Letting CLAUDE.md grow into a project history | Keep it short and specific — it is read on every run and costs tokens each time. |
You should now be able to
- Place a setting in the correct file for its audience and predict which value wins.
- Explain why the shared project file only affects teammates once it is committed, and why the local file is gitignored.
- Pin a model correctly, including what changed with dateless identifiers and where aliases still exist.
- Version prompts and schemas alongside code, and log the combination that produced an output.
- Declare a plugin dependency with a semver range and interpret the common dependency errors.
- Roll a standard plugin set out to an organisation through settings rather than by hand.