Bash permission glob matching breaks when command contains # character
Preflight Checklist
- [x] I have searched existing issues and this hasn't been reported yet
- [x] This is a single bug report (please file separate reports for different bugs)
- [x] I am using the latest version of Claude Code
What's Wrong?
The Bash permission glob matcher treats # as a comment delimiter even when it appears inside quoted arguments. This causes commands that match an allow rule to still trigger a permission prompt if any argument contains #.
Minimal Reproduction
- Add this allow rule to
~/.claude/settings.json:
``json``
{
"permissions": {
"allow": [
"Bash(agent-slack search *)"
]
}
}
- Run a command without
#— auto-allowed, no prompt:
````
agent-slack search messages "test" --channel "fee-strategies" --limit 1
- Run the identical command with
#in a quoted argument — triggers permission prompt:
````
agent-slack search messages "test" --channel "#fee-strategies" --limit 1
The only difference between the two commands is "fee-strategies" vs "#fee-strategies". The first auto-allows; the second prompts.
What Should Happen?
# inside quoted strings should be treated as a literal character, not a comment delimiter. Both commands above match Bash(agent-slack search *) and should auto-allow.
Evidence from Session Logs
Analyzed two independent sessions (~180 JSONL lines total). Both show the exact same pattern:
| Phase | Commands | # in args | Permission prompted? |
|-------|----------|-------------|---------------------|
| Session 1, cmds 1–7 | agent-slack search/user/channel | No | No (auto-allowed) |
| Session 1, cmds 8–11 | agent-slack search ... --channel "#channel-name" | Yes | Yes |
| Session 2, cmds 1–7 | agent-slack search/user/channel | No | No (auto-allowed) |
| Session 2, cmds 8–12 | agent-slack search ... --channel "#channel-name" | Yes | Yes |
The transition from auto-allow to prompting happens precisely when # appears in arguments — not based on invocation count, parallelism, or subagent usage.
Likely Cause
The permission matcher appears to strip everything from # onward (treating it as a shell comment) before matching against the glob pattern, so the effective command becomes truncated and no longer matches.
Related Issues
- #31889 — wildcard permission matching not working (TFS paths with spaces — possibly different root cause)
- #29529 —
Bash(curl *)not matching (could be partially explained by#in URL fragments)
Claude Code Version
2.1.76
Platform
Anthropic API
Operating System
Linux (Arch, 6.19.6-zen1-1-zen)
Terminal/Shell
fish
7 Comments
Found 3 possible duplicate issues:
This issue will be automatically closed as a duplicate in 3 days.
🤖 Generated with Claude Code
Additional context: the permission prompt actually shows the exact reason:
So this is an intentional security check, not an accidental parsing issue. However, it's overly broad — it also triggers for
#inside quoted arguments like--channel "#fee-strategies", where the#is clearly not a shell comment but part of a channel name argument.The security concern about hiding arguments via
#comments is valid for multi-line commands, but single-line commands with#inside quoted strings should be safe to auto-allow.Broader findings: multiple permission prompt triggers
After extensive testing, we identified 4 distinct patterns that trigger manual permission prompts even when the command matches an allow rule like
Bash(agent-slack search *):1.
#anywhere in the command string (original report)Message: "Command contains a quoted newline followed by a #-prefixed line, which can hide arguments from line-based permission checks"
2. Consecutive quotes
''or""/\"\"Message: "Command contains consecutive quote characters at word start (potential obfuscation)"
3. Pipe + mixed quote types
No specific message shown — just the generic "Do you want to proceed?" prompt.
4.
||fallback chains with piped commandsRun commands as separate tool calls instead.
Key observations
|is present AND both'and"appear in the same command&&chains with mixed quotes do NOT trigger (only|pipes)Workaround
We documented these rules in our CLI tool's skill prompt and added a PreToolUse hook that catches patterns 1-3 early with actionable error messages, so the model can self-correct before hitting the permission checker.
Workaround: Use a PreToolUse hook to auto-approve commands that match your intended pattern, bypassing the glob matcher's
#parsing issue:Note: PreToolUse hooks with exit 0 don't override the permission system's deny decisions — they only provide informational output. For true auto-approval, you'd need to rely on the
permissions.allowlist. But since the glob matcher is broken for#, the practical workaround is to use a broader glob pattern:The broader pattern (
agent-slack *instead ofagent-slack search *) avoids the#parsing issue by matching before the#character is encountered. Less precise, but functional until the glob parser is fixed.Thanks for the report. This is fixed in v2.1.89.
All three triggers you hit (
#inside a quoted argument, consecutive''/"", pipe + mixed quotes) came from a set of regex-based pre-checks that ran before allow-rule matching — so yourBash(agent-slack search *)rule was never consulted. v2.1.89 removed those regex checks entirely and replaced them with checks that run on the parsed argument list, so a bare#fee-strategies(no newline) now passes straight through to allow-rule matching.Please upgrade from v2.1.76 and reopen if you still see prompts for these patterns.
This was fixed in v2.1.89 — Bash permission patterns now match correctly when the command contains
#or mixed quote characters inside quoted arguments. If you're still seeing this in the latest version, please comment with your version and repro and we'll reopen.This issue has been automatically locked since it was closed and has not had any activity for 7 days. If you're experiencing a similar issue, please file a new issue and reference this one if it's relevant.