Blog / claude code
Why Claude Code asks about a command you already allowed
Bash(echo:*) runs echo $HOME but not echo $PORT, ${HOME}, $(…), a redirect to a file or a chained rm. What we tested, and the fix for each.
On this page
- What ran and what didn't
- The fixes
- Why not just allow everything?
- When the question comes while you're away
- Frequently asked questions
- Why does Claude Code ask for permission when the command is allowed?
- How do I let Claude Code run a command with an environment variable?
- Do pipes and && need separate permission in Claude Code?
- Why is a redirect to a file blocked in Claude Code?
- Try it with your team
You allowed Bash(npm:*) or Bash(echo:*), and Claude Code still stops to ask about a command that starts with npm or echo. In a claude -p run it's worse: the command is refused, and the agent says something like "echo $PORT was blocked because the tool couldn't check the variable before running it".
A prefix rule covers a plain command. Once the command line contains something Claude Code can't check in advance, such as a variable it can't vouch for, a command inside a command, or a second command you didn't allow, it asks again. We tried each shape with Claude Code 2.1.318 in claude -p, where "ask" means refused.
What ran and what didn't#
| Command | Allowed with | Result | |
|---|---|---|---|
echo hello | Bash(echo:*) | ran | |
echo $HOME | Bash(echo:*) | ran | |
echo $PORT | Bash(echo:*) | asked | |
echo $MY_ | Bash(echo:*) | asked | |
echo ${HOME} | Bash(echo:*) | asked | |
ls *.txt | Bash(ls:*) | ran | |
ls 2>/ | Bash(ls:*) | ran | |
ls > out.txt | Bash(ls:*) | asked | |
cat $(ls) | Bash(cat:*), Bash(ls:*) | asked | |
`cat ls ` | Bash(cat:*), Bash(ls:*) | asked | |
ls && echo done | Bash(ls:*), Bash(echo:*) | ran | |
| `ls \ | wc -l` | Bash(ls:*), Bash(wc:*) | ran |
ls; rm a.txt | Bash(ls:*) | asked |
The pattern:
- Variables.
$HOMEpassed;$PORT,$MY_and evenTOKEN ${HOME}in braces did not. A variable's value isn't known until the command runs, so a rule can't promise what it expands to. - A command inside a command.
$(…)and backticks run something first and paste in the result. Both were refused even thoughcatandlswere each allowed. - Writing a file.
ls > out.txtwrites a file, which is an edit, not just a command. It went through once we also ran with--permission-mode acceptEdits. - Chains.
&&and|between allowed commands ran.ls; rm a.txtdidn't, becausermwasn't allowed. Every part has to be covered.
The fixes#
Read a variable without $. printenv takes the name as a plain argument:
claude -p "Run: printenv PORT. Reply with only its output." --allowedTools 'Bash(printenv:*)'3002Allow the exact command. A rule with the whole command line, variables and all, matches that command and nothing else:
claude -p "Run exactly: echo \$PORT" --allowedTools 'Bash(echo $PORT)'That ran. It's the right tool for one command the agent needs every time, such as a build script that reads $CI.
Let it write files the normal way. If the agent is meant to change files, run it with acceptEdits (or /permissions in a session) rather than allowing shell redirects one by one. Then ls > out.txt stops asking, and so do the agent's ordinary edits.
Split a chain. Ask for the steps as separate commands in your CLAUDE.md ("run each command on its own, never chain with ;"). Each one is then checked against its own rule, which is what you wanted from the rules in the first place.
Take out the $(…). Instead of cat $(ls), have the agent run ls and then read the files it named. Two allowed steps instead of one that can't be checked.
Why not just allow everything?#
--dangerously-skip-permissions makes every prompt go away, including the ones you want, like rm in a chain. The table above is mostly Claude Code being careful in exactly the cases where a prefix rule would have let something through you didn't mean to. A variable can hold a path you didn't expect; $(…) can run anything. Fix the command shape and keep the check.
If prompts in general are slowing you down, our post on Claude Code permission prompts covers allow rules and modes, and the deny rule that didn't work covers the opposite problem.
When the question comes while you're away#
In an interactive session, every one of these is a prompt that waits for you. Joxo shows an agent's question on your phone and sends your answer back into the session, so a single echo $PORT doesn't hold up an hour of work. See approving agent actions from your phone.
Frequently asked questions#
Why does Claude Code ask for permission when the command is allowed?#
The command line contains something an allow rule can't vouch for: a variable such as $PORT, ${…}, $(…) or backticks, a redirect that writes a file, or a second command after ; that isn't allowed. Each needs its own approval.
How do I let Claude Code run a command with an environment variable?#
Use printenv NAME instead of echo $NAME, or allow the exact command line, for example Bash(echo $PORT). In our test the exact rule ran and Bash(echo:*) did not.
Do pipes and && need separate permission in Claude Code?#
Each part needs to be allowed. ls | wc -l and ls && echo done ran with ls, wc and echo allowed. ls; rm a.txt was refused because rm wasn't allowed.
Why is a redirect to a file blocked in Claude Code?#
Writing to a file counts as an edit. With --permission-mode acceptEdits, ls > out.txt ran under a Bash(ls:*) rule; without it, it was refused.