[BUG] A permissions.ask rule containing `:*` other than at the end is silently ignored on the Bash tool

Status Open
Reported on v2.1.224
Maintainer reply ✓ Yes — bcherny
Activity 3 comments · opened Aug 8, 2026
💡 Likely answer: A maintainer (bcherny, collaborator) responded on this thread — see the highlighted reply below.

Preflight Checklist

  • [x] I have searched existing issues and this hasn't been reported yet
  • [x] This is a single bug report
  • [x] I am using the latest version of Claude Code (2.1.224)

What's Wrong?

A Bash(...) permission rule whose content contains the two-character sequence :* anywhere other than as its final two characters never matches anything. There is no error, no warning, and no diagnostic — the rule sits in settings.json looking like a working guard, the file parses, and every other rule in the same list keeps working. claude doctor reports nothing.

The same rule content under PowerShell(...) matches as expected, so the two tool paths disagree.

This is a silent failure of a security control. A rule that is present and inert is worse than a missing one, because the list is what gets audited.

What Should Happen?

Either the rule matches (as the PowerShell path already does), or loading it produces a visible error naming the problem. The binary already contains the diagnostic strings for exactly this case — The :* pattern must be at the end and Move :* to the end for prefix matching, or use * for wildcard matching — but they never reach the user for a rule loaded from settings.json.

Steps to Reproduce

  1. Put this in ~/.claude/settings.json:
{
  "permissions": {
    "defaultMode": "auto",
    "ask": [
      "Bash(git*push* :**)",
      "Bash(git*push* :refs*)"
    ]
  }
}
  1. Start a fresh session in a scratch git repository.
  2. Through the Bash tool, run:
git push origin :refs/tags/some-tag

Both rules describe this command — git*push* :** because * matches any run of characters, git*push* :refs* literally.

  1. Observe that the prompt comes from Bash(git*push* :refs*).
  2. Remove Bash(git*push* :refs*), leaving only Bash(git*push* :**), and start another fresh session.
  3. Run the same command. It executes with no prompt at all.
  4. Repeat steps 1–6 with PowerShell(git*push* :**) and the PowerShell tool. There it does prompt.

Isolation

The colon is not the problem and ** is not the problem — only a * placed directly after a :. All of these fire as expected on the Bash path:

Bash(git*push* :refs*)
Bash(git*push* :m**)
Bash(git*push* :z*)

Only Bash(git*push* :**) is inert.

Impact

There is no way to express "an argument beginning with a colon" as a Bash wildcard rule. * cannot follow the colon, and the rule syntax has no single-character wildcard and no character classes — ?, [, { and | are all escaped to literals by the matcher.

Concretely: a remote branch deletion spelled git push origin :some-branch cannot be guarded on the Bash tool at all, while the --delete and -d spellings of the same operation can. Anyone who writes the colon rule, sees it accepted, and moves on is unprotected and has no way to notice.

Suggested Fix

In order of preference:

  1. Surface the existing validation. The diagnostic text is already written; a rule that fails it should be reported at startup and by claude doctor rather than dropped in silence.
  2. Make the Bash path agree with the PowerShell path and treat :* as a wildcard whenever it is not the rule's suffix.
  3. Failing both, document that :* is reserved anywhere in a rule, so the limitation is discoverable before it is relied on.

Even (3) alone would have prevented this, but (1) is what makes the whole class safe — this cannot be the only rule shape that fails validation quietly.

Environment

  • Claude Code 2.1.224, native build
  • Windows 11 Pro 26200
  • Reproduced through both the Bash and the PowerShell tool, in fresh sessions

How this was found

Not by reading the list — by running the command in a nested claude -p session and reading the permission_denials array in its result object, against a counterfactual run with the rule removed. From inside a session the defect is invisible: a tool result is byte-identical whether no prompt was raised or one was raised and approved.

View original on GitHub ↗

3 Comments

gabgoss · 1 month ago

A data point that may help narrow this: on 2.1.238 / Windows 11,
the diagnostic you describe as missing does surface.

I hit it on a project-level .claude/settings.local.json, in
permissions.allow, with an auto-captured entry whose literal text
happened to contain :* mid-string:

``
Bash(printf '**Scope:** XS9 PhantomSprint\n**Scope:** XS1
StaticVerification\n')
``

The :* here isn't an operator — it's the :** inside the markdown
bold **Scope:**. The warning named the file, the key, and the
offending rule:

``
Settings (…\.claude\settings.local.json › permissions.allow): Invalid
permission rule
"Bash(printf '**Scope:** XS9 PhantomSprint\n**Scope:** XS1
StaticVerification\n')" was skipped:
The :* pattern must be at the end. Move :* to the end for prefix
matching, or use * for wildcard matching
``

So both strings you located in the binary do reach the user in at least
one configuration. Three variables differ from your repro and I
haven't isolated which one matters:

| | Your repro | Mine |
|---|---|---|
| Rule list | permissions.ask | permissions.allow |
| Source file | ~/.claude/settings.json | project
.claude/settings.local.json |
| Version | 2.1.224 | 2.1.238 |

One thing that might be worth checking: the two symptoms may be
different code paths rather than the same bug appearing with and
without a message. Mine was rejected at load — skipped, warned,
never entered the rule set. Yours loaded and then failed to match.
If ask rules bypass the load-time validator and fall through to a
matcher that can't handle the pattern, that would explain both
observations without any version change being involved.

bcherny collaborator · 1 month ago

Reproduced on 2.1.233 (Linux), using the same permission_denials methodology as the report.

With "allow": ["Bash(git:*)"] plus one ask rule, running git push origin :refs/tags/some-tag in a fresh headless session:

  • "Bash(git*push* :refs*)" → the ask rule matches (a permission denial is recorded in -p mode).
  • "Bash(git*push* :**)" → no denial; the command executes with no prompt. The rule is silently inert.

No warning or diagnostic is emitted anywhere when the inert rule is loaded from settings.json.

The permissions documentation states that the :* form is only recognized at the end of a pattern and that a colon elsewhere is treated as a literal character — under that documented behavior the rule above should match this command, so this is a confirmed bug rather than documented behavior: a syntactically accepted security rule that matches nothing and fails without any diagnostic. Marking as a confirmed bug; still present as of 2.1.233 with no related fix in the changelog since.

🤖 Generated with Claude Code

marcindulak · 26 days ago

For your information: the label reproduced on this issue does not prevent it from becoming stale and eventually auto-closed. See https://github.com/anthropics/claude-code/issues/87647 for details, and consider upvoting that for visibility.