Bash permission glob matching breaks when command contains # character

Status Fixed / completed
Reported on v2.1.76
Maintainer reply ✓ Yes — bcherny
Activity 7 comments · opened Mar 14, 2026 · closed Apr 18, 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 (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

  1. Add this allow rule to ~/.claude/settings.json:

``json
{
"permissions": {
"allow": [
"Bash(agent-slack search *)"
]
}
}
``

  1. Run a command without # — auto-allowed, no prompt:

``
agent-slack search messages "test" --channel "fee-strategies" --limit 1
``

  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

View original on GitHub ↗

7 Comments

github-actions[bot] · 5 months ago

Found 3 possible duplicate issues:

  1. https://github.com/anthropics/claude-code/issues/29582
  2. https://github.com/anthropics/claude-code/issues/32876
  3. https://github.com/anthropics/claude-code/issues/31309

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

nikicat · 5 months ago

Additional context: the permission prompt actually shows the exact reason:

Command contains a quoted newline followed by a #-prefixed line, which can hide arguments from line-based permission checks

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.

nikicat · 5 months ago

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)

agent-slack search messages "test" --channel "#fee-strategies"   # triggers
agent-slack search messages "test" --channel "fee-strategies"    # auto-allowed

Message: "Command contains a quoted newline followed by a #-prefixed line, which can hide arguments from line-based permission checks"

2. Consecutive quotes '' or "" / \"\"

python3 -c "d.get('key', '')"                                   # triggers
agent-slack ... | python3 -c "m.get(\"content\", \"\")"          # triggers
python3 -c "d.get('key')"                                       # auto-allowed

Message: "Command contains consecutive quote characters at word start (potential obfuscation)"

3. Pipe + mixed quote types

agent-slack ... | python3 -c "x = 'hello'"                      # triggers
echo '{"a":"b"}' | jq '.a + "x"'                                # triggers
agent-slack ... | python3 -c "x = \"hello\""                    # auto-allowed
agent-slack ... | jq '.messages[] | .ts'                         # auto-allowed

No specific message shown — just the generic "Do you want to proceed?" prompt.

4. || fallback chains with piped commands

agent-slack ... | python3 -c "..." 2>&1 || agent-slack ...      # triggers

Run commands as separate tool calls instead.

Key observations

  • Triggers 1-2 fire regardless of pipes or command structure
  • Trigger 3 only fires when | is present AND both ' and " appear in the same command
  • && chains with mixed quotes do NOT trigger (only | pipes)
  • Commands without pipes can freely mix quote types
  • These checks run before PreToolUse hooks — hooks cannot intercept triggers 1-2 (though they can provide early feedback for 3)
  • All triggers are purely about the command string content, not about runtime behavior

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.

yurukusa · 5 months ago

Workaround: Use a PreToolUse hook to auto-approve commands that match your intended pattern, bypassing the glob matcher's # parsing issue:

#!/bin/bash
# ~/.claude/hooks/auto-approve-slack.sh
# PreToolUse — auto-approves agent-slack commands regardless of # in args

INPUT=$(cat)
TOOL_NAME=$(echo "$INPUT" | jq -r '.tool_name // empty')
[ "$TOOL_NAME" != "Bash" ] && exit 0

COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty')

# Match your intended pattern
case "$COMMAND" in
  "agent-slack search "*)
    exit 0  # Auto-approve
    ;;
esac

exit 0
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          { "type": "command", "command": "bash ~/.claude/hooks/auto-approve-slack.sh" }
        ]
      }
    ]
  }
}

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.allow list. But since the glob matcher is broken for #, the practical workaround is to use a broader glob pattern:

{
  "permissions": {
    "allow": [
      "Bash(agent-slack *)"
    ]
  }
}

The broader pattern (agent-slack * instead of agent-slack search *) avoids the # parsing issue by matching before the # character is encountered. Less precise, but functional until the glob parser is fixed.

bcherny collaborator · 4 months ago

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 your Bash(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.

ashwin-ant collaborator · 4 months ago

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.

github-actions[bot] · 4 months ago

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.