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:
{ "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 -1
if true; then echo y; fi
[[ -f /etc/hosts ]] && echo yes
cat <<< "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
Summary
With
autoAllowBashIfSandboxed: trueand sandboxing enabled, commands containing certain shell constructs prompt for permission despite the setting, with reasons likeContains simple_expansionorUnhandled node type: string. Syntactically-simple commands (literal argv, pipes,&&,;,||, control flow,[[ ]],<<<) auto-approve as expected.Repro
~/.claude/settings.json:{ "sandbox": { "enabled": true, "autoAllowBashIfSandboxed": true } }echo $USER.Contains simple_expansion.echo "user is $USER".echo "$HOME".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 ✗:
echo $USER,ls $HOMEecho "$HOME",echo "$USER"echo $(date)echo $'hello world'echo {a,b,c}Expected
When
autoAllowBashIfSandboxed: trueand 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
autoAllowBashIfSandboxedis 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:--dangerously-skip-permissions, which disables the entire permission system — a massive security regression compared to "sandbox contains everything."Users have to choose between constant prompts or dropping all security. That's a significant workflow regression.
Partial source-level workarounds:
\"\$TMPDIR/file\"works;\"\$TMPDIR\"alone prompts).\$(...), 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
sandbox.enabled: true,autoAllowBashIfSandboxed: true