`--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.
This issue has 5 comments on GitHub. Read the full discussion on GitHub ↗