CCLearn

Shared conventions: what goes in the repo, what stays personal

verified against Claude Code 2.1.261 · 2026-09-0918 min

By the end you can

  • Route each convention to the settings scope whose audience matches who should get it
  • Predict which committed keys reach teammates immediately, which wait for folder trust, and which never apply from a repository file
  • Split a working personal setup into a committed team file and a gitignored local file
  • Choose between a CLAUDE.md instruction and a settings rule based on whether it must be enforced

Your setup works. Eleven levels of it: permission rules you no longer get asked about, a hook that formats on write, a marketplace, a CLAUDE.md that has stopped you re-explaining the build three times a week. So you copy the useful parts into .claude/settings.json, commit, and tell the team to pull.

A week later: two people say nothing changed for them, one says a rule from the file is blocking something they need, and one is on Claude Code on the web where half of it never arrived. You run /status and everything is there. Nothing is broken — every one of those outcomes is the documented behaviour of the scope you picked.

The four files, and who each one reaches

Claude Code reads settings from four files, and an organization can also deliver managed settings from the claude.ai console. Each source has an audience.

FileWho it reachesUse it for
~/.claude/settings.jsonYou, in every project on this machineTheme, editor mode, default model, your own permission rules
.claude/settings.jsonEveryone working in the folder that contains itTeam permissions, hooks, plugins, the environment variables the project needs
.claude/settings.local.jsonYou, in this one project onlyPersonal overrides for one project, and testing before you share
Managed settingsEveryone your organization deploys it to; nothing you set overrides it, apart from a few security-sensitive exceptionsSecurity policy and compliance requirements

The shared file's audience is stated as a folder, not a team:

Everyone working in the folder that contains it. In a git repository, commit it so teammates get it — Settings

Until you commit it, it is a file on your disk like any other and nobody else has it. That is the first half of this lesson's trap; the second half is that committing it is not sufficient either.

Precedence runs the other way from the reading order most people assume. Highest first: managed settings, then command line (--settings and per-key flags, for one session), then project local, then shared project, then user. A key at a higher level overrides the same key anywhere below it.

Committed does not mean applied

Two things keep a key in a committed .claude/settings.json from applying for everyone who clones it, and they fail differently.

Claude Code ignores the key in a repository file. Some keys are read only from your own or your organization's files. The settings index marks them in its Scope column — User, local, or managed, User or managed, Managed, or Global config, the last applying only from ~/.claude.json. None of those apply from the shared file, apart from autoContinueAtUsageLimit, which a repository file can still switch off while no user, --settings, or managed value sets it. That is the whole of the exception, and it is younger than the rule it bends — the key "Requires Claude Code v2.1.234 or later" (Settings reference), so a shared file that names it is doing something only on builds that have it. For every other key, a Scope column reading User or managed means the committed file is ignored, full stop, and a teammate who commits that key to change the behaviour for everyone changes nothing on anybody's machine. The case you have most likely reached for is not in that column at all: permissions.defaultMode has Scope Any file, and it is two of its values that are restricted — auto and bypassPermissions do not take effect from project or local settings. Set them in user or managed settings, or pass --permission-mode for one session. Before v2.1.257, bypassPermissions took effect from any file — so a team file that "used to work" is not evidence that it still does.

The key waits for each teammate to trust the folder. Until they do, they still see prompts and don't get plugins from a marketplace the committed file declares.

From a committed .claude/settings.jsonWhen it applies
permissions.deny, permissions.askImmediately
permissions.allowAfter that teammate trusts the folder
permissions.additionalDirectories, extraKnownMarketplaces, most env valuesAfter that teammate trusts the folder

The docs give the rule behind that split rather than leaving you to infer it:

permissions.allow rules and permissions.additionalDirectories entries in a project's .claude/settings.json grant capability, so Claude Code applies them only after you accept the workspace trust dialog for that folder. … deny and ask rules aren't affected, since they only restrict. — Configure permissions

Do not stretch "grants capability, therefore waits" into a general rule, because hooks break it. A hook in a committed settings file, and a helper command such as apiKeyHelper, are used in both situations where a teammate has not accepted the dialog for that exact folder: when they trusted only a parent folder, and in a claude -p or SDK run, which never shows the dialog at all. Executable content from a repository is not uniformly gated on trust — read the per-content table on Configure permissions row by row.

A second asymmetry explains why your own allow rules always seemed to apply instantly. An untracked .claude/settings.local.json is yours rather than the repository's, so Claude Code applies its allow rules without the trust step it requires for the committed file. Get that file tracked by git and Claude Code treats it as repository-supplied instead, holding its rules until you trust the folder.

Cloud sessions read the clone, not your machine

A cloud session runs on a fresh clone of your repository, not on your machine. The shared .claude/settings.json is read, because the file is part of the clone. ~/.claude/settings.json and .claude/settings.local.json are not — both stay on your machine, and the local file is not in the clone. Of the managed sources, only server-managed settings reach a cloud session; a managed-settings.json file or MDM profile on your device does not.

So short of a managed policy, the docs give two routes into a cloud session: commit the key to the repository's .claude/settings.json, or set an environment variable on the cloud environment. A convention that lives in your user file is a convention your cloud sessions do not have, and /config on the web opens the Claude Code section of your claude.ai settings rather than changing a value for the session.

One placement detail bites in monorepos: Claude Code reads the shared .claude/settings.json from the session's primary working directory, so to use a file committed at the repository root, start Claude Code there. The local file does not follow that rule — in a git repository Claude Code reads and writes .claude/settings.local.json at the repository root even when you start in a subdirectory, so an approval you take in packages/api applies across the whole repository.

That second half is recent, and the docs date it:

Before v2.1.211, Claude Code kept the file in the starting directory. It still reads a file an earlier version left there alongside the root file; where both set the same key, the root's value applies, and permission rules from both files apply. — Settings

Read that second sentence twice, because it is the half that reaches you. Nothing cleaned up the per-directory files earlier builds wrote. A session started in packages/api today reads that subdirectory's leftover file and the root file, and permission rules from both apply — so an approval you granted there on an older build, when Claude Code had nowhere else to put it, is still in force every time you start a session in that directory. It will not be in the file you are looking at. When you have a permission you cannot find a rule for, the root local file is only the first place to look; the second is every subdirectory you have ever started a session in.

The root location has two exceptions, and they are worth knowing before you promise a colleague that one approval covers the repository. The file stays next to .claude/settings.json instead when you are outside a git repository, when the repository root is your home directory, on Windows, and when the repository root or its .git or .claude entry is not owned by your user. And the paths inside it do not anchor at the repository root: a permission rule that starts with /, or a relative sandbox path, anchors at the session's primary working directory instead, so one stored rule covers different files depending on where the session started.

Routing a team convention by audience

Settings rules are enforced by the client; a CLAUDE.md line is context Claude tries to follow. Lesson 6.2 builds that distinction in full. The question here is the team version of it: for a given convention, which audience needs it, and is prose enough for them?

The memory docs route by audience one level up, for an organization deciding what goes into a managed policy, and it is the same split a team makes below it. Their list sends four concerns to settings — blocking specific tools, commands or file paths with permissions.deny; sandbox isolation; environment variables and API provider routing; login method and organization restrictions — and three to a managed CLAUDE.md: code style and quality guidelines, data handling and compliance reminders, and behavioural instructions for Claude (Memory).

The test: if a teammate's session ignoring this would be a serious problem, it is not a CLAUDE.md line. "Never commit to main" in prose is a strong hint; a deny rule is a rule. (What can be enforced against a developer who edits the rule back out is Lesson 12.5; at the repository level, every file here is one a teammate can change.)

The shared CLAUDE.md still matters — the docs say to check it into git so your team can contribute, because it compounds in value over time (Best practices). Its failure mode gets worse precisely when a team edits it: a bloated file makes Claude ignore the instructions that matter, and Lesson 6.6 builds that argument in full. What is new at team scale is who prunes. A file eight people append to has no natural pruner. Somebody owns the cut, or the shared file becomes the mechanism by which the team's real instructions stop being followed — the Lesson 6.6 result, arrived at socially.

Splitting a working setup into team and personal halves

Sort the setup you have by audience. Three destinations. Committed, .claude/settings.json — things true for anyone working on this repository:

json
{
  "$schema": "https://json.schemastore.org/claude-code-settings.json",
  "permissions": {
    "allow": ["Bash(npm run lint)", "Bash(npm run test *)"],
    "deny": ["Read(./.env)", "Read(./.env.*)"]
  }
}

Gitignored, .claude/settings.local.json — same project, only you: your model choice, an approval you took on a prompt, a value you are testing before proposing it. Claude Code writes this file itself when you answer "Yes, and don't ask again", and the first time it writes it in a git repository that does not already ignore it, it adds the path to your global git excludes. If you created the file by hand and Claude Code has not written to it yet, add it to .gitignore yourself — nothing has run that step.

Gitignored, CLAUDE.local.md at the project root — personal instructions rather than settings. The docs describe it as the place for private per-project preferences that shouldn't be checked into version control: your sandbox URLs, your preferred test data. It loads alongside CLAUDE.md and is treated the same way, and you add it to .gitignore yourself.

Splitting is safer than it looks, because list keys such as permissions.allow combine across files instead of one file's value replacing another's — a team allow list adds to a teammate's own rules, which is why the shared file can be small. Four model-list keys are the exception and follow their own rules: fallbackModel, availableModels, modelPicker (v2.1.242 or later), and modelSettings (v2.1.251 or later). Note the direction merging cuts for permissions: a team file cannot take a permission away by omitting it. Only a deny or ask rule does that, and both outrank an allow from a lower file.

Confirming what actually loaded

Three instruments, each answering a different question. /status names the settings files this session read, on its Setting sources line. claude doctor lists the entries Claude Code rejected, with the error. /context lists the instruction files that loaded, under Memory files.

Read the caveat with /status: the line confirms which files Claude Code read, not which file supplied each key. The one exception the docs give is the model — where project or managed settings set it, the startup header names the file that did. For anything else, remove the competing value or walk the precedence stack.

Lesson 5.1 made the general case: a claim and evidence are different objects. Here is the specific instance. "I committed the settings" is a claim about a file. "The team's setup is live" is a claim about other people's machines, and the only thing that tests it is somebody else, on a fresh clone, running /status and then trying the thing the rules were supposed to permit. Build that into the rollout rather than into the announcement — which is Lesson 12.3.

Checkpoint

  1. You commit a .claude/settings.json with a deny rule on .env reads, an allow rule for Bash(npm run test *), and an extraKnownMarketplaces entry for your team's plugins. A teammate clones the repo and starts a session without trusting the folder. Which of the three is in effect, and what do they experience for the other two?

    Show answer

    Only the deny rule on .env reads. deny and ask rules restrict rather than grant, so they apply immediately from a committed file; permissions.allow and extraKnownMarketplaces grant capability, so Claude Code applies them only after that teammate accepts the workspace trust dialog for that folder.

    So they get a prompt every time npm run test runs — the one thing you promised they would stop seeing — and no plugins from the marketplace the committed file declares. The trap is that all three work on your machine: you trusted this folder months ago, and /status on their machine will confirm the file loaded, so "it works on my machine" is evidence about the wrong proposition. Have them trust the folder, then re-test.

  2. A colleague says the team file "isn't working" because their permission mode is still default despite the file setting permissions.defaultMode to auto. What is wrong, and where does that value have to live instead?

    Show answer

    Nothing is wrong with the file — the restriction is on the value, not the key. permissions.defaultMode has Scope Any file, so a committed file may set it; it is auto and bypassPermissions specifically that do not take effect from project or local settings. The tempting wrong diagnosis is "keys like this are ignored from a repository file", which is true of the User or managed rows in the settings index and not of this one.

    Move the value to user settings (~/.claude/settings.json) or managed settings, or pass --permission-mode for a single session. If someone insists the team file used to do this, note that bypassPermissions took effect from any file before v2.1.257 — a memory of it working is not evidence that it still does.

  3. You want every session on this repository on a specific model, and your own sessions on Opus. Which file gets which value, and what happens to a cloud session started on the same repository?

    Show answer

    The repository's model goes in the committed .claude/settings.json; your Opus goes in .claude/settings.local.json. Not in ~/.claude/settings.json — the user file is the lowest level there is, below the file your team commits, so the team's "model" would win over yours. The local file sits above the shared file, so only your sessions change. The model is also the one key /status will help you with: where project or managed settings set it, the startup header names the file that did.

    A cloud session runs on a fresh clone, so it reads the committed .claude/settings.json and gets the repository's model. It reads neither ~/.claude/settings.json nor .claude/settings.local.json — both stay on your machine, and the local file is not in the clone — so your Opus preference does not follow you there. Short of a managed policy, the two routes into a cloud session are committing the key or setting an environment variable on the cloud environment.

  4. Your team's CLAUDE.md says "always run the migration check before touching schema files". Someone's session skipped it and shipped a broken migration. Is that a bug, and what would you change so it cannot recur?

    Show answer

    Not a bug. Settings rules are enforced by the client; a CLAUDE.md line is context Claude tries to follow, and a session ignoring it is the documented behaviour of the scope you chose. The test this lesson gives is the one you skipped: if a teammate's session ignoring an instruction would be a serious problem, it is not a CLAUDE.md line. A broken migration in production qualifies.

    So route it the way the memory docs route blocking specific tools, commands or file paths — a deny or ask rule on the schema paths in the committed .claude/settings.json, with the prose kept as the explanation rather than as the control. Two caveats. At the repository level every file here is one a teammate can edit back out; enforcement against a developer is Lesson 12.5. And check who prunes that shared file: eight people appending to it is how a bloated CLAUDE.md starts making Claude ignore the instructions that matter, which may be why this one was skipped in the first place.

Sources