Why is putting * at the very start or end of a command usually fine, while putting it in the middle (before the subcommand) is especially error-prone?
The key is the subcommand's position. Take Bash(npm run ) as an example: the is at the end, and everything before it — npm run, which includes the subcommand — is written literally and fixed. So this rule is clearly scoped to "anything after npm run," and it won't accidentally allow a completely different subcommand like npm install. The same logic applies when is at the very beginning (like the official example Bash( --version)) — the fixed part sits at the other end, and the actual scope is comparatively easy to read at a glance.
What's genuinely error-prone is a pattern where the subcommand itself falls within the range the matches — like git * main, where the subcommand (whether it's log, merge, or push) sits sandwiched between two fixed pieces of text, and since can match any text, the subcommand is naturally within the range that can be substituted. This pattern visually "looks" constrained at both ends, which easily gives the impression that the middle is somewhat restricted too — but the middle is actually the wide-open part that determines exactly what the command does.
What actual security risk does this create in practice — is it more than just "the rule was written wrong"?
The real risk is that once the subcommand falls within the range * matches, it can end up allowing git operations you never intended to permit at all. Per the official documentation's example, Bash(git * main) allows git -c core.fsmonitor=<script> diff main — a command carrying a -c flag. The -c flag lets git run an external program you specify during execution, which is no longer just "the git scope was written too broadly." It means a rule you thought only covered something like git log ends up opening a doorway that can execute arbitrary external commands.
This is also why official documentation specifically calls out this warning on its own — compared to a simple "the rule is too loose and covers too many git subcommands," the additional risk of this pattern is that it allows variants carrying dangerous flags (like -c), and those variants still superficially look like "a git command related to the main branch," making them unlikely to stand out as anomalous during a rule review.
If my project already has rules written this way, how do I actually check for and fix them?
Step one is watching for whether this warning appears when starting Claude Code after upgrading to 2.1.246 — the warning itself is the most direct check, no need to manually dig through settings files yourself looking for this pattern. If you have an older configuration on hand, you can also directly search settings.json for rules starting with Bash( that contain a * followed by non-wildcard text, paying particular attention to rules that look like they're "scoped to a specific branch" or "a variant scoped to a specific subcommand."
Step two is going back to confirm the original intent behind each rule you find. If the intent was "restrict to a specific subcommand, arguments unrestricted," the correct fix is moving the * to after the subcommand (turning git * main into git log * main, for example). If the intent was genuinely "allow these several git operations as long as the target is the main branch," you may need to reconsider whether this rule should exist at all, because leaving the subcommand unrestricted inherently means the allowed range is far broader than most people would intuitively assume.
Step three, after fixing it, is actually testing a few commands you'd expect to be allowed and a few you'd expect not to be, to confirm the corrected rule's behavior matches your original intent — not just checking that the syntax was changed correctly.
Besides git, does this "* before the subcommand" problem also happen with other commonly used commands?
Yes. This problem is fundamentally unrelated to git itself — it's a matching trap that can occur with any command following a "program name + subcommand + arguments" structure. As long as a tool's behavior is determined by its subcommand (rather than plain flag arguments), putting * before the subcommand lets the subcommand itself fall within the range that can be substituted. Common examples include docker (where docker run and docker exec behave very differently), npm (where npm install and npm run mean completely different things), or any CLI tool with a subcommand structure that shows up in your own project.
A more reliable checking habit in practice is, every time you write a Bash allow rule, to ask yourself "which part of this command is the keyword that actually determines what it does" — and make sure that keyword (usually the subcommand) is written before the , fixed in place, rather than left within the range can match. This principle isn't specific to git — it's a general check applicable to any CLI tool with a subcommand structure.
Claude Code 2.1.246 (part of the August 2026 update sequence) added a startup warning that's easy to overlook but has a real impact on whether your permission rules actually mean what you think they mean: if a Bash allow rule has a wildcard (*) placed before the subcommand — like Bash(git * main) — a warning appears at startup, because this kind of pattern matches a far wider range than intuition suggests. This piece explains what the warning is flagging and how the matching logic actually works underneath it.
Official documentation is direct about this: Bash rules match the whole command text, with * standing in for any text, including spaces. Claude Code treats whatever is written literally before the * as the fixed constraint on the rule — meaning where you place the * directly determines what the rule actually restricts.
Take the command git log --oneline main as an example: git is the program itself, and log is the subcommand — the subcommand, not the program name, is what actually determines what the program does. If you write Bash(git * main), the intent might be "allow various log-related arguments, as long as it ends with main." But Claude Code's actual matching logic treats git before the * as one constraint and main after it as another, with the * in between able to match any text, including the subcommand itself. The actual result: this rule allows git merge main, git push origin main, and even git -c core.fsmonitor=<script> diff main — a command carrying a -c flag that lets git run a program you name — because -c is also within the range * matches. Conversely, the plain command git log main (with nothing in between) is actually not matched by this rule.
To achieve the original intent of "allow log-related commands only, with any arguments," the correct way to write it is putting the subcommand before the *: Bash(git log * main). This way, git plus log together become the fixed constraint, and * only matches arguments between log and main — so git log --oneline main and git log -5 main are correctly matched, while plain git log main (nothing in between) isn't matched by this rule, nor is a completely different subcommand like git push origin main.
The risk from this kind of rule isn't as obvious as "no restriction at all" — a rule with literally zero restriction is easy to spot at a glance. The real risk is that Bash(git * main) looks restricted on the surface (it does contain both git and main, after all), but its actual matching range is far broader than the literal text suggests, and that gap isn't easy to catch by eye during a rule review. What the 2.1.246 startup warning addresses is exactly this gap between a rule looking reasonable and actually being far looser than expected. If you've ever written an allow rule with a wildcard placed before a subcommand, it's worth checking after upgrading whether this warning appears at startup, and re-confirming that the rule's actual matching range matches what you originally intended.