Bash allow wildcards match options before subcommands

Claude CodeAI CodingSecurityDeveloper ToolsCLIGit

August 25, 2026

Dark inspect panel with git, a star-shaped hole, main, sliding -c chips, and the words match options before subcommands

Bash allow wildcards match options before subcommands. Claude Code 2.1.246 started warning about that on 25 August 2026, with Bash(git * main) as the example, because the star was never a branch lock.

The rule looks like a seatbelt for main. Claude Code's permissions docs list git push origin main in the matches column, sitting next to git -c core.fsmonitor=<script> diff main. That second line is not a merge. It is git taking a config override and a hook pathname before it even picks a subcommand.

You'll keep the rule if the new yellow line feels like a nag. The nag is the product telling you the grant is still live.

The star sits where git options sit#

Crumpled printer-paper sketch of a fence labeled git and main with a star-shaped hole in the middle, option flags walking through while a log sign lies on the ground
The star is a hole. git options walk through it. The subcommand sign never got nailed up.

The permissions page says it in one sentence. Put the star after the subcommand.

In git log --oneline main, git is the program and log is the subcommand, the word that picks what the program does. Claude Code matches everything before the first * as written. Those words are the fence. Bash(git log *) is git log. Bash(git *) is every git command. Bash(git * main) is every git spelling that can land main at the end, with the star eating the middle.

git's own synopsis already put the options first. git [-c <name>=<value>] <command>. The -c sits in the hole you drew for a subcommand. The reserved sign on the bumper says main. It does not say log.

The docs table already named the scary row#

No scavenger hunt. The same permissions page publishes the row.

From the official wildcard table

You writeMatchesDoes not match
Bash(git * main)git merge main, git push origin main, git -c core.fsmonitor=<script> diff maingit log
Bash(git log * main)git log --oneline main, git log -5 maingit push origin main

The matching-rule bullets under that table are the sentences that should have been the changelog.

  • The * stands in for whatever text is in its place, including the subcommand and every option before it
  • A trailing * with a space also matches the bare command, so Bash(ls *) matches ls
  • The space before a trailing * is part of the rule, so Bash(ls *) misses lsof and Bash(ls*) does not

The first bullet is the one that names -c. In Bash(git * main) the star stands in for the subcommand, so Claude Code matches every git subcommand and every option before it. That includes -c, which makes git run a program you name.

The git documentation is calmer about the flag. -c passes a configuration parameter to the command. The value overrides configuration files. git-config then says the quiet part about core.fsmonitor. If the value is not the boolean true, the variable contains the pathname of the fsmonitor hook command. That hook is how git finds files that may have changed.

That is the whole receipt. The table already wrote <script>. Nobody needs a working sample, and this post is not going to ship one.

Denying git push does not close this#

Crumpled printer-paper sketch of a DENY PUSH wall stopping a push cart while a -c path walks around it toward a MAIN gate
The deny wall faces push. The -c path never meets it.

The steel-man is reasonable. The same docs page shows deny Bash(git push *) next to allow Bash(git commit *). Rules evaluate deny, then ask, then allow. First match wins. Specificity does not reorder them.

So yes. A deny on git push blocks git push origin main even when the allow git * main would have matched it. That is not nothing.

It is also not the -c row. git -c core.fsmonitor=<script> diff main is not a push. Deny-push never sees it. You'll ship that deny, sleep fine, and still have a hole the table already named.

People will reach for the old command-injection story next, the one where a star ate &&. Claude Code now splits on &&, ||, ;, |, and newlines, and a rule has to match each subcommand. 2.1.246 even started requiring approval for a dangling &&. That class got a real fix. This class got a warning.

Always Allow trained the wrong muscle#

Crumpled printer-paper sketch of a small jobs list line under a coral FAMILY stamp that covers create and delete as well
You ran list. The stamp covered the whole family.

The permission dialog writes the space-separated form when you select Yes, and don't ask again for a command prefix. That is how Bash(git commit *) lands in .claude/settings.local.json. The same click trains a family grant. Bash(git * main) is that shape with the star sitting where a subcommand should sit.

GitHub issue 29187, filed 27 February 2026, is the same click going wider than the line you saw. Always Allow on gcloud scheduler jobs list offered gcloud scheduler:*, which also covers create and delete. The user wanted the list. The dialog offered the family.

GitHub issue 36900 is the other door. A tight Bash(git status:*) missed git -C path/to/the/thing status, because Claude kept inserting -C before the subcommand. The allow looked redundant. The instinct is to open the star until -C fits. git * is that instinct with the safety peeled off.

Same family as the JFrog Boost Always Allow writeup. First token, first click, bigger grant than the line on screen. The wrapper version stamped the binary. This version stamps the hole between git and main.

A warning that still matches is the cost of keeping it#

Crumpled printer-paper sketch of an open gate with a yellow WARNING sign still hanging, and a handwritten git commit star rule on the closed latch beside it
A warning on an open gate is not a lock. The rewrite sits on the latch.

The Claude Code changelog for 2.1.246 is two facts, not one.

  • It added a startup warning for Bash allow rules with a wildcard before the subcommand, example Bash(git * main)
  • It did not strip those rules. Present tense. They also match options inserted before the subcommand

Claude Code already ignores a different broken shape, Bash(command:rm *), and warns at startup because that one was bypassable. If this pattern ever moves into that bucket, the post is wrong and the rule can stay until you rewrite it for taste. Until then, the yellow line is you keeping a grant the docs already enumerated.

Put the star after the subcommand. That is the example the permissions page already prints.

.claude/settings.json
{
  "permissions": {
    "allow": [
      "Bash(npm run *)",
      "Bash(git commit *)"
    ],
    "deny": [
      "Bash(git push *)"
    ]
  }
}

The cost of the position is one extra prompt on git merge. Pay it. A warning on an open gate is not a lock.

Wildcard allow questions

Why does git -C path status miss a Bash(git status *) allow?

Claude often inserts -C before the subcommand. A rule that locks git status as the first two words never sees that line. GitHub issue 36900 is that miss in the reporter's own words. Widening to git * is how people then grant the option hole this post is about.

asked on github.com
Does Yes don't ask again stay on the subcommand you just ran?

Not reliably. GitHub issue 29187 reported Always Allow on gcloud scheduler jobs list offering gcloud scheduler:*, which also covers create and delete. The Claude Code permissions docs say the dialog writes the space-separated prefix form. Treat that click as a family grant, not a blessing of the exact line.

asked on github.com
If git push is already denied, is git star main still a problem?

Deny-first evaluation does block git push origin main even when an allow Bash(git * main) would have matched it. The official table's third match is git -c core.fsmonitor=<script> diff main, which is not a push. A push deny never sees that line.

asked on code.claude.com
Does the 2.1.246 warning turn the rule off?

No. The changelog calls it a startup warning, in present tense, because those rules also match options inserted before the subcommand. A different startup warning ignores Bash(command:rm *). This one still matches until you rewrite it.

asked on github.com
Share

Newsletter

New posts land in your inbox when they publish. No spam, unsubscribe anytime.

Prefer RSS