`--permission-mode dontAsk` bypasses `autoAllowBashIfSandboxed` for Bash commands containing shell variable expansion
Summary
When Claude Code is invoked with --permission-mode dontAsk and sandbox.autoAllowBashIfSandboxed: true, Bash commands that contain shell variable expansion (e.g. $VAR) are denied with "Permission to use Bash has been denied because Claude Code is running in don't ask mode" instead of being auto-approved through the sandbox.
Non-expansion Bash commands run correctly in the same session — so the sandbox is functional and auto-allow fires for those. The issue is specific to commands containing shell expansion.
The sandboxing documentation states:
Auto-allow mode works independently of your permission mode setting. Even if you're not in "accept edits" mode, sandboxed bash commands will run automatically when auto-allow is enabled.
This does not hold for expansion-bearing commands under dontAsk.
Environment
- Claude Code:
2.1.114 - OS: Linux (Ubuntu 24.04 on WSL2)
bubblewrap: 0.9.0socatinstalled
Minimal Reproduction
settings.json:
{
"permissions": { "allow": ["Read", "Glob", "Grep"] },
"sandbox": {
"enabled": true,
"autoAllowBashIfSandboxed": true,
"filesystem": {
"denyRead": ["/"],
"denyWrite": ["/"],
"allowRead": [".", "/usr", "/etc"],
"allowWrite": []
}
}
}
(Run from any git working tree with at least one commit.)
Failing case — command WITH expansion:
export TB=HEAD~1
claude -p "Run the bash command: git log --oneline -1 \$TB" \
--permission-mode dontAsk --settings ./settings.json \
--max-turns 3 --output-format stream-json --verbose < /dev/null
Tool result:
is_error=true: Permission to use Bash has been denied because Claude Code is running in don't ask mode.
Passing case — same session, same settings, same permission mode, but NO expansion:
claude -p "Run the bash command: git log --oneline -1" \
--permission-mode dontAsk --settings ./settings.json \
--max-turns 3 --output-format stream-json --verbose < /dev/null
Tool result:
is_error=false: <commit sha> <commit subject>
The only variable is the presence of $TB in the command string. The sandbox configuration, permission mode, and autoAllowBashIfSandboxed: true are identical.
Expected
Both commands auto-approved by the sandbox per the documented guarantee. Both return git log output.
Actual
- Expansion command: permission-layer deny before the sandbox wrap is attempted.
- Non-expansion command: executed inside the sandbox, output returned.
Impact
Any non-interactive workflow that relies on --permission-mode dontAsk (CI pipelines, scripted headless runs, agents invoked from external automation) cannot execute Bash commands containing shell expansion, even when the sandbox is fully capable of wrapping them safely. Models frequently generate commands with expansion when environment variables are semantically relevant (e.g. git diff \$TARGET_BRANCH...HEAD from a prompt that passes TARGET_BRANCH via env). Workarounds require rewriting commands to read env values separately and inline them, which is not always feasible for generated tool calls.
Likely 2.1.113 provenance
2.1.113 added a family of security hardenings around Bash matching/approval, including:
- \"Security: Bash deny rules now match commands wrapped in \
env\/\sudo\/\watch\/\ionice\/\setsid\and similar exec wrappers\" - \"Security: \
Bash(find:*)\allow rules no longer auto-approve \find -exec\/\-delete\\"
These sibling changes suggest a set of heuristic checks were added that require approval for commands exhibiting certain patterns. If an expansion-heuristic was added at the same time and is evaluated before the sandbox auto-allow path, that would explain the observed behaviour.
Suggested fix
Either (a) evaluate \autoAllowBashIfSandboxed\ for expansion-bearing commands before rejecting under \dontAsk\, or (b) document the expansion-command exception in the sandboxing docs so users know the mode-independence guarantee has a scope.
6 Comments
Found 2 possible duplicate issues:
This issue will be automatically closed as a duplicate in 3 days.
🤖 Generated with Claude Code
This is a good report because it isolates the bug to a very specific mismatch between documented permission semantics and actual command classification.
The documentation promise is clear: sandbox auto-allow should operate independently of permission mode. If that holds for ordinary Bash commands but fails only when shell expansion is present, then the runtime is treating expansion-bearing commands as a different permission class in a way the docs do not describe.
That matters because from the user/operator perspective,
$VARexpansion is a normal shell feature, not a meaningful change in safety model. If the same sandbox and same session auto-allow one command but deny the same command shape with variable expansion, the control plane becomes surprising and hard to reason about.So the core issue is not just a denial. It is inconsistency in the policy boundary. The system is exposing one rule in docs and another in practice depending on syntactic details that users would not expect to matter this much.
Permission systems have to be exact and legible. If shell expansion changes approval behavior, that needs to be either fixed or surfaced explicitly.
Good analysis of the evaluation order issue. The root cause seems clear: expansion detection runs before
autoAllowBashIfSandboxedgets a chance to allow the command.As a workaround until this is fixed upstream, you can use a PreToolUse hook to manually approve expansion-bearing commands when you know they're safe:
This won't fix the underlying permission-layer ordering, but it gives you a way to unblock CI/CD pipelines that depend on
dontAsk+ sandbox.Isn't this the same as https://github.com/anthropics/claude-code/issues/43713 ?
This issue was fixed as of version 2.1.139.
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.