The repository this blog lives in has eight skills checked into git under .claude/. It also has a .claude/settings.local.json of thirty kilobytes holding 354 permission allow rules, and that file is tracked nowhere at all.
That split happened without a decision. The half of my setup that teaches the agent something got shared. The half that grants it something stayed on one machine, accumulating one "yes, don't ask again" at a time.
That split turns out to be the shape of the whole system. A repository can share anything that restricts the agent, and can only ever propose anything that grants it. Once you see that line, deciding what to commit for five developers takes about ten minutes.
What a repo can actually carry
Four things travel in version control, and they behave differently once they arrive:
| What | Where it lives | What it does on clone |
|---|---|---|
| Instructions | CLAUDE.md, .claude/rules/*.md | Loads into context |
| Permission rules, hooks, plugin choices | .claude/settings.json | Partly waits for a dialog |
| Skills | .claude/skills/<name>/SKILL.md | Instructions load, grants wait |
| MCP servers | .mcp.json | Waits for per-person approval |
Skills are the easy win and the reason my repo drifted the way it did. Drop a SKILL.md in .claude/skills/, commit it, and every colleague has your /consolidation-pass on their next session with no install step at all. If you want versioning and reuse across repos you convert it to a plugin later, which trades that zero-friction install for a marketplace entry and a per-developer install command. Start standalone.
The same split runs straight through a single skill file, which is where the docs state the rule most plainly. A committed skill's instructions load for everybody. Its allowed-tools grant, the frontmatter line that lets it run git without stopping to ask, takes effect only after that developer trusts the folder, "the same as permission rules in .claude/settings.json". Anthropic's advice on the same page is to read the project skills before you trust a repository, since a skill can grant itself broad tool access. Teaching travels. Granting waits.
The other three rows are where teams get surprised.
Precedence runs backwards from what you want
Claude Code resolves settings in this order, highest first:
- Managed settings (
/etc/claude-code/managed-settings.jsonon Linux and WSL) - Command line arguments
.claude/settings.local.json.claude/settings.json~/.claude/settings.json
Read rows three and four again. For any setting that holds a single value, the personal file you cannot see beats the team file you reviewed in a pull request. defaultMode is the one that bites: commit plan for the team and a colleague's local acceptEdits quietly wins on their machine, with nothing in the repo to show for it.
Permission rules are the exception, and the exception runs in your favour. They merge across scopes instead of replacing each other, and the deny-first evaluation runs over the merged set. A colleague's local Bash(*) cannot cancel a deny you committed, it only widens what they personally may do. Grants accumulate per person. Restrictions survive the merge.
Only one layer sits above all of them, and it is deliberately not in your repo: managed settings, a root-owned file at a fixed system path, deployed with MDM or Ansible. That is where disableBypassPermissionsMode stops being a preference someone can talk themselves out of. Everything else you commit to .claude/settings.json is a strong default, and a default is exactly as durable as the least patient person on the team.
Grants wait, restrictions don't
Here is the asymmetry the guides skip. permissions.allow rules and additionalDirectories in a project's .claude/settings.json grant capability, so Claude Code applies them only after that developer accepts the workspace trust dialog for the folder. Until then it reads them and ignores them. deny and ask rules are unaffected, because they only restrict.
So the allowlist you committed to save your team from prompt fatigue does nothing on a fresh clone until each person says yes to a dialog listing exactly what the folder wants to grant. The denylist you committed to keep .env out of context works on the first session, for everyone, silently and correctly.
That single rule explains the rest of the surprises:
- A cloned repository cannot approve its own MCP servers. Commit
enableAllProjectMcpServersto project settings and it is ignored in an untrusted folder. The server sits at⏸ Pending approvaluntil a human runsclaudethere and says yes. - Team plugins prompt too.
extraKnownMarketplacesandenabledPluginsin.claude/settings.jsonget your colleagues offered the marketplace once they trust the folder. A plugin from an external source that only the project enables does not load until they install it. - A committed external import gets one shot. If your
CLAUDE.mdimports something outside the working directory,@~/.claude/standards.mdfor instance, the first session shows an approval dialog. Decline it and, in Anthropic's own words, the imports stay disabled and the dialog doesn't appear again. No warning, no second chance, and the colleague who declined has been reading a shorter CLAUDE.md than you ever since.
And the sharp end: in non-interactive -p mode there is no dialog, so the rules stay ignored. Your carefully reviewed allowlist is inert in CI, permanently, without a single line of output saying so. Whatever your pipeline needs to be allowed to do, it needs from managed settings, --settings, or the flags on the command itself.
The gitignore that lives in your home directory
When Claude Code saves a setting to .claude/settings.local.json in a repo that doesn't already ignore it, it adds **/.claude/settings.local.json to your global git excludes. I checked mine while writing this: the line is sitting in ~/.config/git/ignore, and this project's .gitignore has never heard of the file.
Two things follow. A teammate who creates that file by hand, or has the agent write it with the Write tool, gets no exclusion at all, which is how a personal 354-rule allowlist ends up in a pull request. And if the file ever does get committed, its allow rules stop being personal: the repository could have supplied it, so they fall back under the workspace trust check like any project setting.
Put .claude/settings.local.json in the repo's own .gitignore. It costs one line and it stops depending on what every developer happens to have in their home directory.
The memory nobody is sharing
"One CLAUDE.md, five developers" has a hidden second half, because Claude Code has two memory systems and only one of them is yours to commit.
CLAUDE.md is written by you, loads every session, and travels in git. Auto memory is written by Claude, lives at ~/.claude/projects/<project>/memory/, is keyed on the git repository, and is machine-local. It is never shared across machines, so five developers on one repo have five private memory directories that quietly diverge for months.
This is the actual answer to why a colleague's agent seems to know the build quirk yours keeps rediscovering. The knowledge landed in a file that was never going to reach you. When something in there proves generally true, it has to be promoted by hand into CLAUDE.md, which is also the only version anyone can review. And stale entries are worse than no entries once five people are relying on them.
CLAUDE.md is context, not configuration
Anthropic states this plainly in their own docs: CLAUDE.md is delivered as a user message, Claude reads it and tries to follow it, and there is no guarantee of compliance. It shapes behaviour, and enforcement lives in the other layers.
Which means a shared CLAUDE.md fails in the ordinary way shared documents fail, by getting long. Target under 200 lines, because longer files eat context and adherence drops. In a five-person repo you hit that ceiling fast, since everyone wants their concern represented in the file that loads every time.
.claude/rules/ is the fix worth adopting early. One topic per file, and a paths: frontmatter block scopes a rule to the files it actually concerns:
---
paths:
- 'src/api/**/*.ts'
---
# API rules
- Every endpoint validates its input
- Use the standard error response shapeThe backend rules now load when someone touches the backend, and the frontend developer never pays for them. Rules without a paths field load at launch with the same weight as .claude/CLAUDE.md, so you keep the genuinely universal ones there and scope everything else.
Two more team-shaped details. A colleague can add claudeMdExcludes to their local settings and glob your CLAUDE.md out of their sessions entirely, which is a feature in a monorepo full of other teams' files and a surprise anywhere else. And Claude Code reads CLAUDE.md, never AGENTS.md, so a repo where half the team uses a different agent wants a one-line @AGENTS.md import at the top rather than two files drifting apart.
What to actually commit
| Goal | Put it in |
|---|---|
| Conventions, build commands, layout | CLAUDE.md, under 200 lines |
| Rules for one area of the codebase | .claude/rules/*.md with paths |
| Repeatable workflows | .claude/skills/<name>/SKILL.md |
| Things nobody may do | deny in .claude/settings.json |
| Things everyone may do without asking | allow, and expect the dialog |
| Things that must happen every time | A hook |
| Things that must hold against everyone | Managed settings, outside the repo |
| Your own approvals and experiments | .claude/settings.local.json, gitignored |
The row that does the most work is the hook. A PreToolUse hook runs as a shell command at a fixed lifecycle event and applies regardless of what the model decided, and a blocking hook takes precedence over allow rules. It carries the same caveat as everything else here, since a hook out of someone else's repo is executable code and sits behind the same trust gate. When a convention genuinely matters, "we wrote it in CLAUDE.md" is a request and "the hook exits 2" is an answer. Everything I said about layering rules, hooks and the sandbox applies here, with one extra constraint: on a team, the layers that need a human to accept them arrive later than you think.
Anthropic reaches the same gap from the other side. Their field guide to large codebases answers the rollout question with a person rather than a file: someone who owns the configuration and has the authority to make the calls on permissions policy and the plugin marketplace, because without that work "knowledge will stay tribal and adoption will plateau". They report an agent manager role emerging to do it. That is the honest reading of everything above. The layers that hold on their own are few enough that somebody has to keep caring about the rest.
None of this is really about Claude Code. A team that cannot agree on conventions in a code review is not going to agree on them in a CLAUDE.md, and reaching for shared agent config to settle it is the usual move of blaming the tool for the process. What the repo gives you is a place to write the agreement down once, and a much shorter path from "we decided this" to "the agent does this by default on everyone's machine".
Just don't confuse a default with a rule. Two of the things a repo carries hold on their own, the deny list and the hook. The rest is advice, which is still worth writing down. Pick carefully which two or three conventions have earned the enforcement.