CCLearn

Curate the committed allowlist; local grants stay local

Keep .claude/settings.json a curated, reviewed allowlist; let settings.local.json collect exploratory grants, then promote the durable ones and delete the rest. Also gitignore the local file in the repo: Claude Code's auto-ignore goes to your global excludes, which no clone gets.

docs corroborate

The official documentation describes the mechanism this relies on. The technique itself is community practice.

What this costs you if it is wrong

Permission rules are security-adjacent. Promoting a rule into the committed `settings.json` grants it to everyone who clones and trusts the folder, including cloud sessions — promote narrow patterns, never a broad `Bash(*)`. Conversely, a belt-and-braces `.gitignore` line is only cosmetic if your team already relies on the global excludes; it costs nothing but does not retroactively untrack a file already committed.

You have been answering "Yes, and don't ask again" for a few weeks. Open .claude/settings.local.json now and you will probably find a sediment layer: a /tmp rule from one debugging session, a git -C /Users/you/app path that only exists on your laptop, an xargs invocation you ran once, a fetch domain for a vendor doc you read in March. None of it was ever reviewed. All of it was granted one prompt at a time.

That file is supposed to be yours alone. The convention worth adopting is to keep it that way on purpose, and to treat the other file — the committed .claude/settings.json — as something you edit deliberately, never something that grows by accretion.

The split

.claude/settings.json.claude/settings.local.json
Who it affectsEveryone who clones the repo, including cloud sessionsYou, on this machine, in this repo
How entries get inYou write them, in a reviewed commitClaude Code writes them, one "don't ask again" at a time
What belongs thereThe build wrapper, gh commands the team's workflow needs, read-only git inspection, the project venv, documentation fetch domains/tmp paths, machine-specific absolute paths, one-off shell pipelines, your personal fetch domains
Before allow rules applyEach person must trust the folder itself, not just a parentApplies without the trust step — while the file stays untracked
In gitCommittedNot committed

The practice on top of that: every so often, read your local file. For each rule ask whether the whole team would want it. If yes, move it into the committed file as a narrow pattern and say so in the commit message. If no — and most will be no — delete it. You lose nothing; the next prompt re-grants what you actually still need.

Where the automatic protection actually lives

This is the part that surprises people. Claude Code does keep the local file out of your commits, but not the way you would guess. The docs say it plainly: the first time Claude Code writes .claude/settings.local.json in a git repository that does not already ignore it, it appends **/.claude/settings.local.json to your global git excludes file — core.excludesFile if your global config sets one to an absolute or ~-prefixed path, otherwise $XDG_CONFIG_HOME/git/ignore or ~/.config/git/ignore.

Global means per-machine. It is not in the repo, so it does not arrive with a clone. The docs already name the gap: if you created the file by hand and Claude Code has not written to it yet, you must gitignore it yourself.

The scary version of this is overstated — here is the accurate one

You will see this framed as "a committed settings.local.json silently grants your teammates everything you ever approved." That is not what current Claude Code does, and the correction matters more than the scare.

When the local file is tracked in git, Claude Code stops treating it as yours and treats it as repository-supplied: its allow rules are held until you accept the workspace trust dialog for that folder, and the dialog lists the rules the folder wants to grant so you can read them first. Same gate the committed settings.json gets. (Before v2.1.207, an untracked file's rules applied before you accepted the dialog — so version matters here.)

So the real failure mode is narrower and more human:

  • Someone accepts a trust dialog without reading the list, which is most people, most of the time.
  • The rules then apply repo-wide, on every clone, indefinitely — including ones that only ever made sense on one laptop.
  • Nobody reviews them again, because nothing prompts a review of a file that arrived pre-approved.

Doing it

Check your current state first:

bash
git check-ignore -v .claude/settings.local.json
git ls-files --error-unmatch .claude/settings.local.json 2>/dev/null \
  && echo "TRACKED — this is the problem case" \
  || echo "not tracked — good"
cat ~/.config/git/ignore 2>/dev/null

If the file is tracked, git rm --cached .claude/settings.local.json and commit, then add the ignore line. Removing it from the index does not delete your local copy or your grants.

Then read the file and sort it. A rule earns promotion if it is (a) machine-independent, (b) something a new teammate would need on day one, and (c) narrow enough that you would approve it in a code review with no context. Everything else gets deleted.

What is unverified here

The mechanism is documented; the practice is not.

  • Documented: the two-file split and what each is for, that "Yes, and don't ask again" writes to the local file, the global-excludes behaviour with the exact file-resolution order, and that a tracked local file is held behind workspace trust. All of that is in the settings and permissions pages.
  • Not documented anywhere official: the accretion failure mode, the promote-and-prune review habit, and the specific category split in the table above. Those come from a team codifying it in a public issue thread, not from Anthropic.
  • Nobody has run a controlled test of the teammate case — specifically, what a clone does on a machine where Claude Code has never written a local settings file. The docs imply the first write protects that machine too, which makes the exposure window narrower than the "your teammates are unprotected" framing suggests.

What would settle the last point: on a machine with no ~/.config/git/ignore and no core.excludesFile, clone a repo, hand-create .claude/settings.local.json, and run git status before ever letting Claude Code write to it. If it shows up as untracked-and-visible, the hand-created gap is real and the .gitignore line is doing work.

Checkpoint

  • Is .claude/settings.local.json currently tracked in your repo? What command told you?

    Show answer

    Only your repo can answer that, and two commands settle it: git ls-files --error-unmatch .claude/settings.local.json succeeds only when the file is tracked, and git check-ignore -v .claude/settings.local.json names the ignore file covering it, if any exists.

    Tracked is the problem case. Claude Code then stops treating the file as yours and treats it as repository-supplied, holding its allow rules behind the workspace trust dialog. Fix it with git rm --cached .claude/settings.local.json, commit, then add the ignore line — removing it from the index does not delete your local copy or your grants.

  • Which file does Claude Code's automatic ignore land in, and why does that not help a teammate?

    Show answer

    Your global git excludes: core.excludesFile if your global config sets one to an absolute or ~-prefixed path, otherwise $XDG_CONFIG_HOME/git/ignore or ~/.config/git/ignore. Global means per-machine — the line is not in the repo, so it does not arrive with a clone.

    There is a second gap in the same mechanism: it is written only the first time Claude Code itself writes the local settings file in a repo that does not already ignore it. A file you created by hand, before Claude Code ever wrote to it, is not covered at all, and the docs say to gitignore it yourself. One line in the repository's own .gitignore closes both, for everyone, in every clone.

  • Name one rule in your local file you would promote, and one you would delete — and say why the promoted one is safe for everyone.

    Show answer

    The specific pair is yours to pick, but the test is fixed. A rule earns promotion only if it is machine-independent, something a new teammate would need on day one, and narrow enough that you would approve it in a code review with no context. Promotions look like the build wrapper, gh commands the team's workflow needs, read-only git inspection, the project venv, or a documentation fetch domain. That three-part test and those categories are one team's convention codified in a public issue thread, not anything Anthropic documents; what the docs cover is the two-file split itself.

    Deletions are the sediment: a /tmp rule from one debugging session, a git -C /Users/you/app path that exists only on your laptop, an xargs pipeline you ran once, a personal fetch domain. "Safe for everyone" means narrow — promoting grants the rule to everyone who clones and trusts the folder, including cloud sessions, so never promote a broad Bash(*). Deleting costs nothing; the next prompt re-grants whatever you still need.

Where this came from

Official pages that back this