Claude Code saves a "Yes, and don't ask again" approval as an allow rule in .claude/settings.local.json at the root of your git repository. That file is meant to stay on your machine: Claude Code adds it to your global git excludes the first time it writes the file, but if you created the file yourself, or anything in it reaches a commit, the rules and any keys typed into them can be published with your code. This guide explains what the file holds, when it is and is not kept out of git, how a secret gets into it, and what to do if one already has. It was checked on November 9, 2026 against Anthropic's, GitHub's and Git's own documentation.
What settings.local.json is for
Anthropic's settings documentation describes .claude/settings.local.json as the project-local scope: personal overrides for one project, and a place to test settings before you share them in .claude/settings.json. The shared file is meant to be committed so a team gets the same rules. The local file is not.
Three things usually end up in the local file:
- Saved permission rules. When you choose "Yes, and don't ask again" for a Bash command, Claude Code saves that approval as an allow rule here. The permissions page lists the same behavior for WebFetch domains and web search.
- An
envblock. The settings page says anenvblock in a settings file is an ordinary key. Settings files are plain JSON on disk, so anything you put there is stored as text. - Hand-written overrides, such as MCP or permission settings you are trying out before sharing.
Where "don't ask again" rules are saved
According to the permissions documentation, a permanently saved approval goes to .claude/settings.local.json at the root of the git repository, even if you started Claude Code in a subdirectory. The rule then applies to future sessions anywhere in that repository, including subdirectories and worktrees. The table in that documentation lists Bash rules as permanent per repository and command, and WebFetch rules as permanent per repository and domain.
Two details matter for review:
- File edits are not saved. The documentation says a file-modification approval is not written to the file and lasts only until the session ends.
- Compound commands become several rules. Approving a command made of several parts saves a separate rule for each part that needed approval.
There are exceptions, for example outside a git repository and on Windows, where the rule is stored elsewhere. The settings page lists them; check it if your setup is one of those cases.
Is it kept out of git?
Usually, but not always. The settings documentation says that the first time Claude Code writes the file in a git repository that does not already ignore it, it adds **/.claude/settings.local.json to your global git excludes file. If you create the file by hand, the documentation says to add it to .gitignore yourself.
The global excludes file belongs to your user account, not to the repository. Git's documentation describes it as the file named by core.excludesFile, by default $XDG_CONFIG_HOME/git/ignore or ~/.config/git/ignore, and contrasts it with .gitignore, which is the file that travels with the repository when it is cloned. In practice that means (ours):
- The protection lives on one machine. A teammate's clone, a new laptop or a container only has it once Claude Code has written the file there too.
- A file created by hand, by a script or by another tool before Claude Code touched it is not covered.
git add -f, or a broad add after the excludes file changed, can still stage it.
Adding .claude/settings.local.json to the repository's own .gitignore closes all three gaps for everyone who clones the project. It costs one line.
The documentation also notes a trust difference: allow rules in the local file apply without the workspace-trust step, while a tracked file goes through that step. So a local file that becomes tracked is not only a leak risk, it also changes how its rules are treated.
How a secret ends up in the file
The saved rule is built from the command you approved (ours, from how the documentation describes saving approvals). If that command contained a credential, for example a token in a curl header, a password in a database connection string or an inline API_KEY=... before a script, the credential can end up in the rule text. Nothing about the prompt makes that obvious at the moment you click.
The other common route is the env block. It is convenient for giving Claude Code an environment variable, and it stores the value in plain JSON.
On November 8, 2026 a security vendor's report on agent permission files found in public GitHub repositories drew a large discussion on Hacker News. We could not read the report itself, so this post does not repeat its numbers; the advice below rests on the vendor documentation only.
A short checklist
- Add
.claude/settings.local.jsonto the repository's.gitignore(ours; the documentation says to do this for hand-created files). - Run
git ls-files .claudeandgit log --all -- .claude/settings.local.jsonto see whether the file is, or ever was, tracked (ours). - Open the file and read every allow rule. Delete rules that contain tokens, passwords or full connection strings, and rules broader than you meant (ours).
- Do not type credentials into commands you approve with "don't ask again". Use environment variables set outside the project or a secrets manager, and refer to the variable name (ours).
- Keep keys out of the
envblock of any settings file that might be shared or committed (ours). - Deny reads of secret files. The settings documentation gives
Read(./.env)andRead(./.env.*)in adenylist as an example, and the permissions documentation says deny rules are checked before ask and allow rules (docs). - Keep GitHub push protection on. GitHub's documentation says push protection for users is enabled by default and stops you pushing supported secrets to public repositories on GitHub.com; push protection for a repository is off by default and has to be turned on by an admin (docs).
If a key was already committed
Treat it as exposed. GitHub's secret scanning documentation says scanning runs automatically on public repositories, that some partner secrets are reported directly to the issuing provider, and that you should rotate an affected credential immediately. Removing the line in a new commit does not remove it from history or from clones, forks and caches (ours). The order that works:
- Revoke or rotate the credential with its provider.
- Check the provider's logs for use you do not recognise.
- Remove the file from tracking (
git rm --cached .claude/settings.local.json), add it to.gitignore, and commit. - Decide whether to rewrite history. It helps for private repositories, but it does not undo a leak from a public one, which is why rotation comes first.
What this does not solve
Keeping the file out of git stops one kind of leak. It does not limit what an approved command can do on your machine, and it does not stop an agent from reading a secret that is on disk and sending it somewhere it is allowed to reach. For those, see how to sandbox an AI agent, prompt injection and the AI agent security checklist. If the agent talks to MCP servers, the MCP security checklist covers token handling on that side. For company-wide rules that developers cannot override, see Claude Code managed settings.
How we approach this
This is our approach, not a rule. On client projects we commit a reviewed .claude/settings.json with the team's allow and deny rules, ignore the local file in the repository's own .gitignore, keep credentials in environment variables that come from a secrets manager, and run a secret scanner in CI as well as relying on GitHub's. "Don't ask again" is fine for commands that never carry a credential; for anything that does, we would rather answer the prompt each time.
Sources: Claude Code, settings, Claude Code, permissions, GitHub, about push protection, GitHub, about secret scanning, Git, gitignore. All read November 9, 2026. Tool behavior changes often, so check the current pages before configuring.