Skip to content
audiotale.net
Sign in
GuidesGuide

Claude Code "don't ask again" rules: what settings.local.json saves and how to keep secrets out

Choosing "Yes, and don't ask again" writes a permanent rule to a project file; here is what the vendor's docs say it stores, where it can leak, and five fixes.

audiotale.net team

· 8 min read

Written by AI from the sources linked below. How we use AI

On this page
  1. In short
  2. Why this is in the news
  3. What "Yes, and don't ask again" actually saves
  4. Why a rule can contain a secret
  5. How the file is supposed to stay private
  6. Five steps to keep secrets out
  7. 1. Look at the file you already have
  8. 2. Do not approve permanently what has a secret in it
  9. 3. Block reads of secret files
  10. 4. Make sure the local file is ignored in the repository itself
  11. 5. Turn on a safety net at the push
  12. What this does not cover
  13. Related reading from us
  14. What we don't know yet
  15. What to watch next
  16. Sources

In short

When you pick "Yes, and don't ask again" on a shell command in Claude Code, the tool saves a permission rule to a file named .claude/settings.local.json at the root of your git repository. That file is meant to stay out of version control, but it is plain text, it can hold the exact command you approved, and it only stays private if git ignores it. To keep secrets out of it: never let a command with a password or token inline be approved permanently, add Read deny rules for your .env files, make sure the file is ignored, and turn on push protection on GitHub.

Why this is in the news

A post on Hacker News today pointed to a report from the security firm Apiiro, titled about coding agents leaking secrets on GitHub through "Don't Ask Again". We could not open that report (its site blocked our automated request), so we do not repeat its numbers here and cannot confirm its findings. What we can do is explain, from the vendor's own documentation, what that button does and how to make sure it never puts a credential in a public place. Everything below comes from the sources linked at the end.

What "Yes, and don't ask again" actually saves

Claude Code asks before it runs most shell commands. According to the permissions documentation, a shell (Bash) command needs approval unless it is in a built-in set of read-only commands, and choosing "Yes, and don't ask again" saves that approval "permanently per repository and command". The same table says a web fetch approval is saved permanently per repository and domain, a web search approval permanently per repository, and a file edit approval only until the session ends.

Where does the saved rule go? The documentation says that for approvals that save permanently, "such as for a Bash command or a WebFetch domain", Claude Code writes the rule to .claude/settings.local.json at the root of the git repository, resolved through worktrees to the main checkout. The rule then applies to future sessions anywhere in that repository, including subdirectories and worktrees. In some cases, such as outside a git repository or on Windows, the documentation says the rule is saved elsewhere, and points to a section called "Where Claude Code looks for each file" for the exceptions.

So the file is a list of things you told the tool it may do without asking. That is the useful part. The risky part is what the rule text looks like.

Why a rule can contain a secret

Permission rules are written as a tool name and a specifier. The documentation gives Bash(npm run build) as a rule that "matches the exact command npm run build". It follows that a rule saved from an approval names the command you approved. If the command you approved contained a literal value, for example an API key passed on the command line or in an inline assignment, we would expect the saved rule to contain that text too. This is our reading of how exact-match rules work, not a statement the documentation makes about secrets, and we have not tested every case. The practical conclusion does not depend on it: treat the file as something that can contain whatever you typed into a command.

Two details in the documentation limit the damage but do not remove it:

  • For a leading assignment of an environment variable, the documentation says Claude Code strips only certain "known-safe" variables when matching allow rules, so that Bash(npm test *) matches NODE_ENV=test npm test, and that "an allow rule won't match past an assignment of any other variable".
  • When you approve a file path permanently, Claude Code escapes gitignore pattern characters such as [, ] and * in it, so that the rule matches only the literal path you approved.

Neither changes the main point: the approved text is stored in a file in your project folder.

How the file is supposed to stay private

The settings documentation lists four kinds of settings files. The shared project file .claude/settings.json is meant to be committed so teammates get it. The project local file .claude/settings.local.json is "you, in this one project only", and "Claude Code keeps it out of git when it creates the file".

The mechanism is specific. The first time Claude Code writes the local file in a git repository that does not already ignore it, it adds **/.claude/settings.local.json to your global git excludes file. That is the file named by core.excludesFile if your global git config sets it, otherwise $XDG_CONFIG_HOME/git/ignore, or ~/.config/git/ignore when that variable is unset. The documentation adds: "if you create it by hand, add it to .gitignore yourself".

That is where the gaps are. Each of these is a situation the documentation itself describes or implies, not a bug report:

  1. You created the file by hand. Nothing ignores it until you do.
  2. Someone else cloned your repository, or you use another machine. The ignore entry lives in your global git excludes on one machine. A fresh checkout on a different machine, or a different user account, does not carry it.
  3. The file is already tracked. The documentation notes that if the file is tracked by git, it is treated differently for trust purposes. Ignore rules do not remove a file git already tracks, so a tracked copy keeps being committed.
  4. You publish more than git history. Other packaging steps (for example building an archive or a package) have their own include and exclude lists, separate from git's.

None of this is unique to Claude Code. Any local config file that records commands can leak what is typed into it. The difference with an agent is volume: it runs many commands, and the approvals pile up in a file you rarely open.

Five steps to keep secrets out

1. Look at the file you already have

Open .claude/settings.local.json in each project and read the allow list. Any rule containing something that looks like a key, token, password or connection string should be deleted, and the secret itself should be treated as exposed and rotated if the repository was ever public or shared. Claude Code's /permissions command shows every rule and which settings file it comes from, which is a quicker way to review them.

2. Do not approve permanently what has a secret in it

When a prompt shows a command with a literal credential, choose the one-time approval. Better, change how the command gets its secret: read it from an environment variable that is set in your shell, or from a file that is not committed, rather than typing it into the command line. The documentation says that sometimes a prompt offers only a one-time approval, with no "don't ask again" option, and that you can add a rule yourself in /permissions when you want a standing one. Writing a rule by hand lets you use a pattern such as Bash(npm run test *) that contains no secret at all.

3. Block reads of secret files

The settings documentation gives this example for stopping Claude Code reading .env files:

{
  "permissions": {
    "allow": ["Bash(npm run lint)", "Bash(npm run test *)"],
    "deny": ["Read(./.env)", "Read(./.env.*)"]
  }
}

The permissions page explains that deny rules are evaluated first, in the order deny, ask, allow, and that an allow rule cannot carve an exception out of a deny rule. It also says Claude "makes a best-effort attempt" to apply Read deny rules to all its built-in file-reading tools, to @file mentions and to files an IDE shares. "Best-effort" is the vendor's word, so a deny rule is one layer, not a vault. The same page says a .claudeignore file "has no effect", so entries there should be moved into Read deny rules.

4. Make sure the local file is ignored in the repository itself

Add this line to the project's .gitignore, so the protection does not depend on one machine's global excludes:

.claude/settings.local.json

Then check that git agrees: run git check-ignore -v .claude/settings.local.json (it prints the rule that matches) and git ls-files .claude (it lists any file in that folder that git already tracks). If the local file is tracked, remove it from the index with git rm --cached .claude/settings.local.json and commit that change. Removing it from future commits does not remove it from history, which is why step 1 says to rotate anything exposed.

5. Turn on a safety net at the push

GitHub's push protection blocks pushes that contain hardcoded credentials before they reach the repository. Its documentation says that "push protection for users" is on by default on GitHub.com and stops you pushing secrets to public repositories, while "push protection for repositories" is off by default and needs GitHub Secret Protection to be enabled by a repository administrator, organization owner, security manager or enterprise owner. It covers partner patterns (provider-specific credentials), generic patterns (such as private keys and database connection strings), and custom patterns you define; AI-detected passwords are a public preview feature. Anyone with write access can bypass a block by giving a reason, and each bypass creates an alert and an audit log entry. A scanner only catches formats it knows, so it complements the steps above and does not replace them.

What this does not cover

  • Other agents. Codex, Cursor and other tools keep their own approval lists in their own files. Check where each one stores them.
  • Secrets in the conversation. A key pasted into a prompt is sent to the model provider and may sit in session logs on your machine. A settings file is only one place a secret can end up.
  • Past exposure. Ignoring a file now does not help if an old commit already contains it. Search history and rotate.

Agent configuration files are a recurring weak spot. We reported on how an AGENTS.md file in a Downloads folder can steer Codex and Claude Code, on a backdoored Claude Code extension on the Cursor Marketplace, and on an alignment test in which agents leaked a private token in 15% of swarm-forum runs.

What we don't know yet

  • Whether the Apiiro report's numbers hold up. We could not read it, and we found no second outlet that had reproduced its figures.
  • How often real-world rules contain literal secrets. We describe how the mechanism could expose one; we have not measured how often it does.
  • Whether future Claude Code versions will warn when a command to be saved permanently looks like it contains a credential. The documentation we read does not mention such a check.

What to watch next

Anthropic's documentation changes with each release, and the permissions page already shows version-dependent behavior (some checks require v2.1.290 or later). Read the page again after upgrades. If Apiiro's report becomes available to us we will compare its method to the mechanism described here and add an update.

Sources

Written by

audiotale.net team

The official account of the people who run audiotale.net: announcements, conversation starters and the newsroom's drafts. Posts written with AI say so. Every other account here is a real person.