[BUG] Wrong command is reported in "don't ask again for" in "This command requires approval"

Status Closed — not planned
Reported on v2.1.50
Maintainer reply None cached
Activity 10 comments · opened Feb 21, 2026 · closed Mar 25, 2026

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?

When executing bash with a pipe, where the first pipe argument should be auto-allowed, but the second isn't, like this:

Bash command                                                                                                                                                                
                                                                                                                                                                             
   hyprctl clients -j | python3 -c "import sys,json; [print(f'at=({c[\"at\"][0]},{c[\"at\"][1]})') for c in json.loads(sys.stdin.read()) if 'popup' in c.get('title','')]"   
   Check popup position after manual move                                                                                                                                    
                                                               
 Do you want to proceed?
 ❯ 1. Yes                    
   2. Yes, and don’t ask again for: hyprctl clients:*
   3. No

 Esc to cancel · Tab to amend

It asks to allow hyprctl clients:*, even though hyprctl clients:* is allowed:

> \cat .claude/settings.local.json
{
  "permissions": {
    "allow": [
      "mcp__web-search-prime__webSearchPrime",
      "mcp__web-reader__webReader",
      "Bash(head:*)",
      "Bash(tail:*)",
      "Bash(tail *)",
      "Bash(flutter pub get)",
      "Bash(flutter build linux --release)",
      "Bash(grep:*)",
      "Bash(grep *)",
      "Bash(flutter build:*)",
      "Read(//home/username/.pub-cache/hosted/pub.dev/**)",
      "Bash(flutter clean)",
      "Bash(cmake *)",
      "Bash(dart analyze *)",
      "Bash(hyprctl clients:*)",
      "Bash(hyprctl monitors:*)"
    ]
  }
}

Python is not allowed, so the Bash tool call is probably blocked because of python, not because of hyprctl clients.

What Should Happen?

TUI should display the correct prompt:

 Bash command

   hyprctl clients -j | python3 -c "import sys,json; [print(f'at=({c[\"at\"][0]},{c[\"at\"][1]})') for c in json.loads(sys.stdin.read()) if 'popup' in
   c.get('title','')]"
   Check popup position after manual move

 Do you want to proceed?
 ❯ 1. Yes
   2. Yes, and don’t ask again for: python3:*
   3. No

 Esc to cancel · Tab to amend

Error Messages/Logs

Steps to Reproduce

  1. Start in a new project
  2. Ask it to run a piped command of your liking, where both parts will not be allowed
  3. Allow the first one
  4. Ask it to rerun this command
  5. The first command will still be there, even though it is allowed.

Claude Model

Opus

Is this a regression?

Yes, this worked in a previous version

Last Working Version

_No response_

Claude Code Version

2.1.50 (Claude Code)

Platform

Anthropic API

Operating System

Other Linux

Terminal/Shell

Other

Additional Information

OS: Arch Linux, EndeavourOS flavor, super custom but I don't think that it should matter.
Terminal: kitty
Default shell: zsh
Sorry if it's a dupe, I searched but couldn't find the similar issue.

View original on GitHub ↗

10 Comments

github-actions[bot] · 6 months ago

Found 2 possible duplicate issues:

  1. https://github.com/anthropics/claude-code/issues/11007
  2. https://github.com/anthropics/claude-code/issues/16180

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

SivkovSavely · 6 months ago

Possible duplicates are not relevant. The second command is not executed and the first one seems related but is closed.

jamesarosen · 6 months ago

There's also an inconsistency about spaces vs colons that happens in these cases.

I have Bash(git log *) in my allow list. Claude has no trouble with simple git log ... commands.

But if Claude tries to do

git log main..HEAD --format="%H %s" | while read hash msg; do … git diff-tree … done

it raises this permissions check:

git log main..HEAD --format="%H %s" | while read hash msg; do echo "=== $hash $msg ==="; git diff-tree --no-commit-id -r --stat $hash; echo; done
   Show files changed in each commit since main

 Do you want to proceed?
 ❯ 1. Yes
   2. Yes, and don’t ask again for: git log:*
   3. No

https://github.com/anthropics/claude-code/blob/main/plugins/plugin-dev/skills/command-development/references/frontmatter-reference.md suggests the colon, which is consistent with the message, but it doesn't explain what the colon means. Is it a case of bash -c separating the command from the arguments?

fedosov · 6 months ago

Root Cause Analysis

I reproduced this bug by analyzing how tree-sitter-bash (the parser Claude Code uses for bash command analysis) builds the AST for compound commands.

Why It Happens: Bash Operator Precedence in the AST

The key insight is that | (pipe) has higher precedence than || and && in bash. This means tree-sitter-bash parses:

hyprctl clients -j | python3 -c "..."

as:

pipeline
  command ("hyprctl")
  command ("python3")

But for commands with redirects + logical operators + pipe:

wg show 2>/dev/null || wireguard-go --help 2>&1 | head -5

the AST becomes:

pipeline
  redirected_statement          ← NOT a "command" node!
    list
      redirected_statement
        command ("wg show")     ← The real first command (buried inside)
      command ("wireguard-go")
    file_redirect (2>&1)
  command ("head")              ← The only direct "command" child of pipeline

The Bug

The prefix extraction algorithm appears to search for the first direct child of type command inside a pipeline node — without recursing into wrapper nodes like redirected_statement or list.

Since redirected_statement is not a command type, it gets skipped. The algorithm then finds head (or tail, grep, python3) — which is the right side of the pipe — and uses that as the prefix.

Verified with tree-sitter-bash

I confirmed this AST structure using web-tree-sitter@0.24.7 + tree-sitter-wasms:

const Parser = require('web-tree-sitter');
await Parser.init();
const parser = new Parser();
const lang = await Parser.Language.load('tree-sitter-bash.wasm');
parser.setLanguage(lang);
const tree = parser.parse('wg show 2>/dev/null || cmd --help 2>&1 | head -5');
// Print AST → confirms pipeline → redirected_statement → list → command structure

| Command | Prefix shown | Should be |
|---------|-------------|-----------|
| wg show 2>/dev/null \|\| cmd 2>&1 \| head -5 | head:* | wg:* |
| bun init -y 2>/dev/null && bun add ... 2>&1 \| tail -5 | tail:* | bun:* |
| cat file 2>/dev/null \| head -5 | head:* | cat:* |
| ls -la 2>&1 \| grep pattern | grep:* | ls:* |
| git log ... \| while read ... (from @jamesarosen's comment) | while:* or similar | git:* |

Suggested Fix

When traversing a pipeline node, if a direct child is not a command/declaration_command, the algorithm should recurse into it rather than skip it. This way redirected_statementlistcommand chains are properly followed, and the first real command in the pipeline is found.

Pseudocode:

find_command(node):
  if node.type in {"command", "declaration_command"}: return node
  if node.type in {"pipeline", "redirected_statement"}:
    for child in node.children:
      if child.type in {"command", "declaration_command"}: return child
      found = find_command(child)   // ← THIS RECURSION IS MISSING
      if found: return found
    return null
  // default: recurse into all children
  for child in node.children:
    found = find_command(child)
    if found: return found
  return null

TDD Validation

I built a test suite (13 tests, node:test + real web-tree-sitter parser) comparing the current behavior vs the fixed algorithm. All 13 pass — the fix resolves the bug without breaking simple commands, &&/|| lists, semicolons, or declaration_command (export, local). Happy to share the repo if helpful.

fedosov · 6 months ago

TDD test suite for this bug (13 tests, all green): https://gist.github.com/fedosov/b155ad5b0fe016375dcd65322147dd2a

npm install && npm test

Tests use web-tree-sitter + tree-sitter-bash to parse real bash commands and compare the current algorithm (reproduces the bug) vs the fixed one.

insidewhy · 6 months ago

I'm seeing the opposite thing, for example somecmd | tail -n 3 will keep asking me to allow tail even though tail is already allowed. It's very very frustrating because claude keeps bothering me incessantly. I'm not sure if it's because somecmd isn't allowed or what, but over and over again, with many different commands that | tail ... will ask to be allowed (and also commonly for grep).

Caleb-KS · 6 months ago

<img width="1125" height="398" alt="Image" src="https://github.com/user-attachments/assets/8643de9c-77f8-4316-9fbb-7d3591158120" />

github-actions[bot] · 5 months ago

Closing for now — inactive for too long. Please open a new issue if this is still relevant.

fedosov · 5 months ago

The bug is still there. See the comment above for a solution. Please reopen the issue.

github-actions[bot] · 5 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.