◉CLAUDE LABJP
●2.1.302 — Fork subagents are now on by default in claude -p, the Agent SDK and the desktop app. Turn them off with CLAUDE_CODE_FORK_SUBAGENT=0 (10/16)●2.1.299 — Conversations over 200K tokens left idle about 54 minutes get compacted; MCP allow rules now match only the server they name●SONNET 4.5 — Retires on the Claude API on 11/30, 43 days left. Migrate to Sonnet 5.5●WINDOWS — Reports say hooks break silently only on Japanese-locale Windows; check the character encoding first●FAST MODE — Opus 5.5 fast mode is out of research preview and no longer needs the beta header (10/15)●NEW — I Put a Ten-Line Promise File at the To…●2.1.302 — Fork subagents are now on by default in claude -p, the Agent SDK and the desktop app. Turn them off with CLAUDE_CODE_FORK_SUBAGENT=0 (10/16)●2.1.299 — Conversations over 200K tokens left idle about 54 minutes get compacted; MCP allow rules now match only the server they name●SONNET 4.5 — Retires on the Claude API on 11/30, 43 days left. Migrate to Sonnet 5.5●WINDOWS — Reports say hooks break silently only on Japanese-locale Windows; check the character encoding first●FAST MODE — Opus 5.5 fast mode is out of research preview and no longer needs the beta header (10/15)●NEW — I Put a Ten-Line Promise File at the To…
Articles/Claude Code
⟐ Claude Code/2026-10-18Intermediate

I Added the allow Rule and the Prompt Still Appears — Reading Six Layers from /permissions

When an allow rule is in your settings and the same approval prompt keeps coming back, something else decided first. Starting from what /permissions shows, here are six layers you can verify against the official permissions docs: ask and deny precedence, where settings.local.json is read, Cowork's shell tool, and more.

Claude Code265Permissions8settings.local.jsonTroubleshooting22

It was a late evening, and I had just added my site's build command to the allow list. I saved the file, went back to the session, and asked for the same thing again. The same approval prompt came up.

I reopened the settings file, hunted for a missing bracket, and added and removed the * from the pattern. About thirty minutes later I noticed what I had skipped: I had never looked at the list of rules that were actually in effect. I kept doubting whether what I had written was correct, and never checked whether it had been loaded at all.

Here is what I'd like to say first. When an allow rule seems to be ignored, it is usually because another layer reached a verdict before allow was consulted. Below, I walk through six such layers, using only what the official permissions documentation lets you verify, and I start from what /permissions shows.

The evaluation order, first

According to the docs, rules are evaluated in the order deny, ask, allow, and the first match decides the outcome. Specificity does not change that order.

So if Bash(aws *) is denied, a narrower Bash(aws s3 ls) in allow does nothing. The same goes for ask and allow: a matching ask rule still prompts even when a more specific allow rule exists. Allow is the last layer, and it only speaks for calls that neither deny nor ask matched.

Step 0: open /permissions and read where each rule came from

This single command is the entry point.

/permissions

The dialog lists every permission rule and the settings.json file each one comes from. You can open it while Claude is working, and a rule you add or remove applies from the next tool call. Before v2.1.234, the change waited until the turn ended.

Look for two things only.

  • Is the allow rule you wrote in the list at all? If not, the file was not loaded (checks 3 to 6).
  • If it is there, is there an ask or deny rule that matches the same call (checks 1 and 2)?

Check 1: an ask rule matches the same call

Sometimes an ask rule matches the very command you allowed.

{
  "permissions": {
    "allow": ["Bash(npm run build)"],
    "ask":   ["Bash(npm run *)"]
  }
}

With this configuration, npm run build hits the ask rule first and keeps prompting, even though the allow rule is narrower. The fix is to narrow the ask pattern or remove it. Adding more allow lines changes nothing on screen.

If the prompt appears for a compound command that chains cd and git, it may be a design choice rather than a missing rule. I traced that one in The Same Approval Prompt for "cd, git add, git commit" Every Time.

Check 2: a deny rule in another scope

Deny rules work across settings scopes. The docs say that if user settings allow something and project settings deny it, the deny wins. The reverse holds too: a user-level deny blocks a project-level allow.

One more detail is easy to miss. A deny that names only a tool, such as Bash, removes the tool from Claude's context entirely. A deny with a pattern merely blocks matching calls when they are attempted. If what you see is "the tool is simply not there" rather than a prompt, suspect the first kind.

Check 3: where settings.local.json is read from

When your allow rule is missing from /permissions, suspect the file's location first.

Per the documentation, .claude/settings.local.json is loaded from the git repository root even if you start Claude Code in a subdirectory. Before v2.1.211, only the current working directory counted, so a file placed deeper in the repository may never have been seen on an older version. The docs also note that on Windows, or outside a git repository, Claude Code may not use the repository root.

~/.claude/settings.local.json is read only when you start a session in the home directory itself. A rule meant for every project belongs in ~/.claude/settings.json.

Check 4: a settings.local.json tracked by git

This file is meant to be yours alone. If git tracks it, Claude Code treats it as supplied by the repository and holds its allow rules until you trust the folder. The same applies when .claude is a symlink.

I wrote up the decision itself, whether to believe an allow that lives in the repository or to move trust into the environment, in Trusting an allow in the repository versus registering trust in the environment. Here I'll only suggest checking with git ls-files whether the file is tracked.

Check 5: file edit approvals are never saved

Sometimes "it asks every time" is simply how saving works. In the docs' table, a Bash command or a WebFetch domain approved with "don't ask again" is saved to settings.local.json. A file modification, on the other hand, lasts only until the session ends.

If you want edits to stop prompting, you have to write an Edit allow rule yourself. Pressing the approval button repeatedly leaves nothing behind in any file.

Check 6: is the tool being run the one your allow names?

The last one is a little different. In a Cowork session, shell commands run through a tool called mcp__workspace__bash rather than the built-in Bash tool. The docs say a deny rule on Bash also applies to that tool, but a Bash allow rule is never carried over.

So an allow that works in the CLI can stay silent in Cowork. Read the tool name on the prompt once and confirm whether it says Bash or mcp__workspace__bash.

Hooks belong in this layer as well. A hook that exits with code 2 blocks the call before allow rules are even evaluated. Conversely, a PreToolUse hook returning allow does not skip the deny and ask rules. If you run hooks, they are part of the investigation.

A read-only script that lists every rule by file

Opening each file by hand is tiring, so I use the script below to see where allow, ask and deny live in one pass. It changes nothing.

#!/usr/bin/env bash
# List permissions from the settings files in and around the repo (read-only)
ROOT="$(git rev-parse --show-toplevel 2>/dev/null || pwd)"
for f in "$HOME/.claude/settings.json" \
         "$ROOT/.claude/settings.json" \
         "$ROOT/.claude/settings.local.json"; do
  [ -f "$f" ] || { echo "(missing) $f"; continue; }
  echo "== $f"
  python3 - "$f" <<'PY'
import json, sys
try:
    p = json.load(open(sys.argv[1])).get("permissions", {})
except Exception as e:           # a missing bracket shows up here
    print("  cannot parse JSON:", e); sys.exit(0)
for kind in ("deny", "ask", "allow"):   # printed in evaluation order
    for rule in p.get(kind, []):
        print(f"  {kind:5} {rule}")
PY
done
# If git tracks the file, its rules are held until you trust the folder (check 4)
git ls-files --error-unmatch .claude/settings.local.json 2>/dev/null \
  && echo "WARNING: settings.local.json is tracked"

I print deny, ask, allow in that order so the evaluation order is visible at a glance. If the same pattern appears under both deny and allow, you already have your answer. A missing bracket surfaces as the cannot parse JSON line.

Why I took the long way around

That evening, the only response I had to the problem was to add allow lines. Every time it seemed not to work, I stacked one more similar pattern, until I could no longer say which of them was doing anything. The result was poor.

These days I open /permissions before adding anything and read which layer decided that call.

Before adding a rule, read the layer that already decided. I try to hold to that order even on days when I'm in a hurry.

If your rule list has grown long, When a session loaded with allow rules gets heavier every turn may help with the cleanup.

The next time the same prompt appears, I'd suggest opening /permissions before the settings file, and looking for the source of that one line. Where the source is, the fix usually is too.

Share

Thank You for Reading

Claude Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.

  • ✦Copy-paste ready implementation code
  • ✦New advanced guides published daily
  • ✦$5/mo or $10 for lifetime access
View Membership →

If you found this article helpful, a small tip ($1.50) would mean a lot to us. Your support helps keep this site ad-free and covers server and hosting costs.

Related Articles

⟐ Claude Code2026-10-13
The Same Approval Prompt for 'cd, git add, git commit' Every Time — Following It to a Design Choice, Not a Missing Setting
With a compound command like cd /path && git add && git commit, Claude Code keeps asking for approval even with allow rules in place. Reading the official permissions page next to a GitHub report shows that cd combined with git prompts by design. Here is how to tell, in what order to check, and four ways to cut the prompts.
⟐ Claude Code2026-09-10
When Every Request Fails With Not signed in to the Cloud gateway, Check the Version Before the Config
A Claude Code 2.1.265 regression broke every request for gateway and proxy setups. I use it as an excuse to build a small snapshot of the auth input surface, so the next time something breaks you can tell config from version in seconds.
⟐ Claude Code2026-09-05
My deny rule was watching a filename, not the routes that reach it
One line in deny let several other spellings of the same file walk right past. Here is the small matrix I build to list every route to a protected file, and how I re-check it on upgrade day.