[BUG] autoAllowBashIfSandboxed bypassed for commands containing shell expansions and several other constructs
Summary
With autoAllowBashIfSandboxed: true and sandboxing enabled, commands containing certain shell constructs prompt for permission despite the setting, with reasons like Contains simple_expansion or Unhandled node type: string. Syntactically-simple commands (literal argv, pipes, &&, ;, ||, control flow, [[ ]], <<<) auto-approve as expected.
Repro
~/.claude/settings.json:
``json``
{ "sandbox": { "enabled": true, "autoAllowBashIfSandboxed": true } }
- Start a Claude Code session.
- Ask Claude to run
echo $USER. - Observe: permission prompt, reason:
Contains simple_expansion. - Ask Claude to run
echo "user is $USER". - Observe: auto-approved.
- Ask Claude to run
echo "$HOME". - Observe: permission prompt, reason:
Unhandled node type: string.
Observed behavior matrix (v2.1.92, macOS)
Auto-approved ✓:
ls /tmp,cat /etc/hosts,date; uptime | tail -1if true; then echo y; fi[[ -f /etc/hosts ]] && echo yescat <<< "hello"echo "user is $USER",echo "$HOME/x",echo "h=$HOME"(string with literal content + expansion)
Prompts ✗:
| Command | Reason shown |
|---|---|
| echo $USER, ls $HOME | Contains simple_expansion |
| echo "$HOME", echo "$USER" | Unhandled node type: string |
| echo $(date) | Contains command_substitution |
| echo $'hello world' | Contains ansi_c_string |
| echo {a,b,c} | Contains brace_expression |
Expected
When autoAllowBashIfSandboxed: true and sandboxing is enabled, commands that would run inside the sandbox should auto-approve regardless of whether a static analyzer can prove them safe. The sandbox is the security boundary — that's the documented purpose of the setting. Failure to statically analyze a command shouldn't weaken the sandbox trust model.
Impact — this is severe for automated workflows
autoAllowBashIfSandboxed is the standard configuration for running Claude with meaningful autonomy while keeping a real security boundary. It's what lets an agent run an edit → test → fix loop unattended. Shell variables and command substitutions appear in practically every realistic test/build command (go test -coverprofile=\$TMPDIR/x.cov, python -m pytest --basetemp=\$(mktemp -d), cargo test --target-dir \"\$CARGO_TARGET_DIR\", etc.), so in practice this bug means:
- Every edit+test loop now hits a prompt per iteration. Unattended runs stall.
- The only remaining way to run unattended is
--dangerously-skip-permissions, which disables the entire permission system — a massive security regression compared to \"sandbox contains everything.\" - The documented safe middle ground (sandbox + auto-allow) no longer exists in practice.
Users have to choose between constant prompts or dropping all security. That's a significant workflow regression.
Partial source-level workarounds:
- Quote the variable AND ensure the string has literal content (
\"\$TMPDIR/file\"works;\"\$TMPDIR\"alone prompts). - No workaround for
\$(...), brace expansion, ANSI-C strings, heredoc bodies, etc.
Suggested fix
When sandboxing is enabled and autoAllowBashIfSandboxed: true, check sandbox auto-allow before returning \"ask\" for commands the static analyzer can't handle. The sandbox contains the blast radius regardless of what the parser couldn't prove.
Environment
- Claude Code CLI v2.1.92 (macOS, Apple Silicon)
sandbox.enabled: true,autoAllowBashIfSandboxed: true
35 Comments
Forgot to mention in the original report — this worked as expected about a week ago. I noticed prompts appearing for commands that previously auto-approved. Flagging in case this helps narrow down which change introduced it.
Found 2 possible duplicate issues:
This issue will be automatically closed as a duplicate in 3 days.
🤖 Generated with Claude Code
+1 seeing similar behaviour with same settings flags, was working as of 2.1.90 afaik
So damn annoying. OpenCode way better, but Anthropic closing down...
Same issue in Linux btw.
This is so frustrating. I use a sandbox not just for security, but specifically to bypass those Bash compound commands that constantly ask for permission.
Now that this bug has appeared, the entire point of using the sandbox is completely gone.
This is affecting me as well, and is quite annoying. One hacky way I've found to work around it is to add a
PreToolUsehook that parses the tool call arguments to see if"dangerouslyDisableSandbox"is omitted/falseor set totrue. If it's omitted or set tofalse, the hook allows the command, which means no permission prompt in the sandbox. Otherwise the hook should signal that it doesn't have a particular opinion about the tool call, which should cause Claude Code to fall back to the standard permission system (permission prompts when appropriate outside of the sandbox).My personal
PreToolUsehook does a bunch of unrelated stuff related toghCLI commands, so I won't post it here, but I bet that Claude Code could come up with a minimal hook (though it's frustrating that it's necessary in the first place).The main downside of this approach is that you need to make sure you only use it when sandboxing is enabled. If you use it somewhere where sandboxing isn't enabled,
"dangerouslyDisableSandbox"will not be present forBashtool calls, and they'll all be (very dangerously!) auto-approved.Of course, the ideal solution would be for Anthropic to fix this themselves. Short of that, this is the best hacky solution I've found for this issue
Workaround:
NB: I think they changed the default effort level in recent versions, so you might want to increase effort to "high" after the downgrade.
(I don't get how bugs like this get through in a supposedly dog-fed product like Claude Code? Anthropic does not use the sandbox themself?)
Confirming this also reproduces on 2.1.89 from the
claude-code(stable) Homebrew cask on macOS, not just 2.1.92+ /claude-code@latest. Same config (sandbox.enabled: true,autoAllowBashIfSandboxed: true), same repro:echo $USERprompts withContains simple_expansion, whileecho "user is $USER"auto-approves. So the regression landed earlier than the original report suggested, and the stable channel is affected too — downgrading toclaude-code@latestpre-2.1.92 isn't enough; you'd need to go back further on the stable cask (2.1.87 was the prior stable release).Further narrowed: 2.1.87 is clean (same config, same
echo $USERrepro, no prompt). So the regression landed in 2.1.88 or 2.1.89. Theclaude-codestable cask jumped 2.1.87 → 2.1.89 directly, so anyone on stable can't test 2.1.88 without a manual install; a user onclaude-code@latestwould need to verify which of the two introduced it.+1 I want to use patterns like $PROJECT_ROOT but get asked to OK it every time e.g:
<img width="460" height="354" alt="Image" src="https://github.com/user-attachments/assets/ee1d7581-dddd-4b80-9c2e-53938a1356e4" />
tell claude to use xargs instead.
this also triggers it
<img width="837" height="349" alt="Image" src="https://github.com/user-attachments/assets/fb006515-3829-410f-923e-815f09cb8bd0" />
Still happening in 2.1.114. Prompts because of
Contains simple_expansioneven withautoAllowBashIfSandboxed = truePlease make Claude with sandbox usable again. Still stuck to 2.1.87 because of this and #43454.
This is a big deal, I completely agree with the other comments. Claude code always comes up with "complex commands" so the current permission system results in constant approval requests. The sandbox isn't perfect (read all by default ??) but provided a reasonable state and clear blast radius.
Running with --dangerously-skip-permissions is the alternative but that requires an external sandbox or docker to control the blast radius.
So please do one of (but really, ideally, all of):
The only options I can think of today:
I'm also dying to know how Anthropic's own employees go around this ? The build system is such that it's only a few commands and nobody there sees this problem ? What are we all missing in this thread ?
Additional impact: plugin/skill
!backtick patterns are fully blockedThis parser bug isn't just cosmetic for plugin
!backtick expressions — it's a hard blocker that prevents skills from loading.The compound-engineering plugin uses
!backtick patterns in skill files to pre-resolve values at load time. For example, inskills/ce-compound/SKILL.md:When this pattern hits the permission checker, it fails with:
The skill cannot execute at all — the error fires before any permission matching happens, even with
Bash(git rev-parse:*)in the allowlist.Affected skills:
ce-compound,ce-sessions, and any skill using worktree-safe repo name resolution via!backtick patterns.Environment: Claude Code on macOS (Darwin 24.6.0), compound-engineering plugin v3.2.0
+1 I use the sandbox to be able to not constantly babysit permissions.
When Claude tries to run
find ... -exec ..., it similarly gets blocked and requests permission to run with the messagefind with '-exec' executes commands or modifies files — cannot be auto-allowed by a Bash(find:*) prefix rule. If the command had just run inside the sandbox it would have succeeded, as it was not modifying any files outside the current dir (and usually it isfind ... -exec grep ...anyway)When claude writes temp files I find that it will often reach for
$TMP, which also triggers this. For this specifically I have resorted to telling it to write temp files to a literal./tmpdirectory in the current dir.I have also experienced all the other cases mentioned so far.
The
Contains expansionhook also fires on"${VAR}"-- the bash form that uses braces toexplicitly delimit the variable name boundary and double quotes to prevent word-splitting. This
form is often cited as the safest variable expansion style, yet it triggers the prompt while the
less-explicit
"$VAR"(no braces) does not.Reproduction:
Expected: both forms are already quoted; neither would cause word-splitting or glob expansion.
The hook should treat them identically.
Observed:
"${VAR}"triggersContains expansion;"$VAR"passes without a prompt.The practical effect is that users who write the more defensive form get more confirmation prompts
than users who omit the braces. Not certain whether this fits within #43713's scope or warrants
a standalone report -- mentioning it here for triage.
This bug is now a month old... It basically make the sandbox useless without using hook hacks. How is this possible? I can not see how the fix is even hard to fix. Just accept any bash action inside the sandbox... (yes, that means I accept the risk of the agent trying to break out, but that is just the tradeoff of using this kind of sandbox)
I'm getting extremely tired of bugs like this being long-lived and unacknowledged...
Finally:
Fixed autoAllowBashIfSandboxed not auto-approving commands with shell expansions like $VAR and $(cmd))
(NB: have yet confirmed it fixes all the cases)
Not fully fixed :/
v2.1.139:
I can confirm that 2.1.139 fixed many of the permission issues I had, it's really nice!
There are still a few patterns that are still triggering a prompt though, here's a recap of the most common ones
| Pattern | Example | Native message |
|---|---|---|
| for-loop var as standalone |
for sha in a b c; do git log -1 $sha; done|Contains simple_expansion(bare$sha) /Contains shell syntax (string) that cannot be statically analyzed("$sha") /Contains expansion(${sha}) /Contains for_statement(when the for-loop is wrapped in a pipe, e.g.… \| head) ||
X=$(cmd)then"$X"later |BODY=$(gh issue view 1); gh issue edit 1 --body "$BODY"|Contains shell syntax (string) that cannot be statically analyzed|| inline
VAR=val cmd|GIT_SEQUENCE_EDITOR="sed -i '1s/pick/edit/'" git rebase -i HEAD~3|This command requires approval||
while readloop var as standalone |gh pr view … --json commits \| while read oid; do git show $oid; done| (same as for-loop) ||
find -exec|find /p -name "*.vue" -exec grep X {} \;|find with '-exec' executes commands or modifies files — cannot be auto-allowed by a Bash(find:*) prefix rule||
IFS=|find … \| while IFS= read -r f; do …; done|IFS assignment changes word-splitting — cannot model statically||
while … <<<|while read x; do echo "$x"; done <<< "hello"|Contains while_statement||
cd+ output redirection |cd /repo && yarn --version > /tmp/out.txt|Compound command contains cd with output redirection - manual approval required to prevent path resolution bypass||
cd+git|cd packages/backend && git checkout dev|This command changes directory before running git, which can execute untrusted hooks from the target directory.|What is the rationale for not blanket allowing all commands with auto-allow? I don't understand why auto-allow in sandbox is not a simple flag.
Auto edit (or a command which allow to write to file) + auto-allow give the model the option to write whatever to a file and run that file, no?
Where is the rationale for this complexity?
I agree that having the agent respect deny patterns can be good to control the workflow, but I just can't see the reason for the current state at all.
Per the docs, sandboxed commands run without prompting -- only two named exceptions: deny rules, and rm/rmdir on critical paths. In practice there's a third exception: commands containing certain shell constructs. Is this a bug, or an undocumented feature?
still biting in 2.1.140
I've filed #58214 for the specific issues around
FOO=barand similar patterns within arguments.Please fix this ASAP, it completely breaks sandboxed autonomous coding, and it's a trivial fix.
When setting
autoAllowBashIfSandboxed: "truethe harness must NEVER stop to ask permission for shell calls that don't want to disable the sandbox.That's the whole point of having a sandbox...
I found a workaround for latest versions:
which looks like in nix + homemanager:
Which you can also apply in non-nix configs:
settings.json:/nix/store/qdb1j3zq96f7w9j5480vqfdnppagq3rl-claude-pretooluse-gate:Claude is a mess in this regard. The decision fatigue makes it useless for me. It seems to spend most of its effort coming up with elaborate one-liners or shell scripts that require approval (the Claude-assisted config files I've set up).
What's the point of using the sandbox if this isn't fixed? It's so unusable currently, it prompts me all the time for permissions
I wish I could go back safely to OpenCode without risking my account banned. Claude Code is just bad, they don't address issues, unless you know someone to hit via private channels, or when it blows up on social media. Pointless to report issues or care at this point.
Motivated by this super frustrating issue, I ended up making my own solution to this - very generic and satisfying and easy to use cli sandboxing app for Linux users using Linux Containers (LXC's; Incus in particular). In my opinion, it is much better than their /sandbox could be even in theory. I will release it open source as soon as I can (and as soon as I get permission from my employer) and will post a link here. As it's holiday season things could be still delayed perhaps month or two...
The solution that has worked for me is to add a hook that matches the most common and annoying culprits directs Claude to pay attention to the relevant instructions I've given in CLAUDE.md and skills.
So far it has worked like a charm, and with the added bonus that all the opaque (and blocked, and impenetrable, and ephemeral) shell commands have been replaced with saved Python scripts (in a hidden scratch directory) that I can examine and even potentially reuse.