Your Claude Code Permissions File, and What Every Line Does

Arun Nagarathanam · 31 Aug 2026 · Template · 53 of 59 in this course

An exclusive resource for students of Arun's Claude Code Course.

Nobody writes a permissions file from an empty page, so this is your starting point. Copy it into a folder called .claude at the top of your project and save it as settings.json. Rules you want in every project you ever open go in a .claude folder in your home directory instead. Both files are called settings.json.

{
  "$schema": "https://json.schemastore.org/claude-code-settings.json",
  "permissions": {
    "deny": [
      "Read(.env)",
      "Read(.env.*)",
      "Read(secrets/**)"
    ],
    "ask": [],
    "allow": [
      "Bash(git add:*)"
    ],
    "disableBypassPermissionsMode": "disable"
  }
}

Read the rest of this page once, and then you never have to treat that file as a mystery again.

The three lists, in the order they are checked

Claude checks deny first, then ask, then allow. The first rule that matches decides the outcome. A more specific rule does not jump the queue.

That order is why the file is written in that order. It reads down the page the way the system thinks.

ListWhat it meansWhat it costs you if you get it wrong
denyNever, under any circumstanceA deny you regret cannot be overridden by anything, from anywhere. Not by another file, not by a flag, not by you in the moment
askStop and check with me firstAn ask you regret prompts you forever, and there is no not-this-time option on it
allowGo ahead without askingAn allow you regret takes about 10 seconds to narrow or delete

Line by line

"Read(.env)"
Your .env file is where API keys live. Claude reads files inside your working folder without any approval box appearing, which is deliberate, because being asked about every file would make the tool unusable. It also means an empty deny list is not a decision you get to make about your keys. This line makes it one.

"Read(.env.*)"
Catches every suffixed version of that file, like .env.local or .env.production.

"Read(secrets/**)"
Covers a folder called secrets that you may not have made yet. The two stars mean everything inside it, at any depth.

"ask": []
Empty on purpose. In the mode you are starting in, everything that can bite already stops and asks you, so a rule here today would be doing nothing. Fill it when you move to a looser mode.

"allow": ["Bash(git add:*)"]
One line, and the surprise is what is not in it. Every config you find online puts a dozen more up there, listing files, printing a file, searching, checking what changed. Claude Code already runs all of those without asking, in every mode, because it recognises them as read-only. Those lines change nothing. They sit in the file looking like protection and the only thing they do is teach you the file is working when it is not.

A line earns its place by changing what happens. Staging earns it, because staging writes.

The :* on the end is a wildcard, and it stands for whatever comes after the command. When Claude saves a rule for you it writes that wildcard as a space instead of a colon. Both spellings mean the same thing.

"disableBypassPermissionsMode": "disable"
Blocks bypass permissions inside this project and its subfolders. Claude shows a short message and opens in manual mode instead. Run the terminal command from somewhere else and Claude can still open with bypass on, so treat this as a guard on this project rather than on your machine.

The line that should be yours

Add your own commit rule at the bottom of the allow list:

"Bash(git commit:*)"

That one is deliberately not in the file above. It is the one decision in there that belongs to you rather than to me.

Watch the commas. The last line in a list does not take one, and the line above it now does. That is the single thing people get wrong, in both directions.

Before you write any rule

Only write it if you can name the thing exactly. The exact command, or the exact path.

A vague rule either never fires, or fires on something you never meant it to catch. Neither of those failures tells you it happened.

When a rule you wrote correctly does not fire

Three things decide whether your rule applies, and none of them is the text of the rule.

  1. Where you started Claude. Whatever folder you were in when you typed claude is your working directory. Reading needs no approval inside it. Step outside and the same read stops and asks you, and your settings file has nothing to do with it. Fix it with /add-dir, a flag at launch, or an additionalDirectories entry in your settings file.
  2. Whether you accepted the workspace trust dialog. Until you do, the allow rules and additional directories in that project get read and not applied. Deny and ask are untouched by this, because those two only take permission away.
  3. What the rule was aimed at. If something worked once and then started asking, the permission you were relying on arrived attached to a single action rather than to your file, and it cleared the moment you sent your next message.

Quick diagnosis: if Claude is asking before it reads a file, that is the folder, and adding more allow entries will change nothing. If Claude is stopping on one particular command, that is the rules.


See Where You Stand

This resource is one piece of a much bigger system.

Take the free Claude Code Readiness Quiz. It is 15 short situations, scored out of 100, and it tests how you handle a job rather than what you know about a tool, so you can take it without ever having opened Claude Code.

Take the Claude Code Readiness Quiz →

15 situations · Scored out of 100 · Free