[BUG] Sandboxed bash does not recognize `!` and gives "command not found"
Preflight Checklist
- [x] I have searched existing issues and this hasn't been reported yet
- [x] This is a single bug report (please file separate reports for different bugs)
- [x] I am using the latest version of Claude Code
What's Wrong?
The Claude Code sandbox does not recognize the bash ! keyword (pipeline negation operator). Instead of negating the exit status of the following command, the sandbox treats ! as a literal command name, fails with !: command not found, and never executes the command that follows it.
Any bash construct that uses ! — if !, while !, bare ! — will silently fail to produce side effects (file creation, copies, writes, directory creation) while the rest of the script continues. The command after ! is never invoked at all.
This is not the same bug as the known pipe-dropping issue (#16305). In #16305, commands execute but stdout is silently dropped. In this bug, the command never executes — the sandbox intercepts ! before bash can parse it as a shell keyword. The stderr output !: command not found confirms this, since normal bash never produces that error for ! (it's a reserved word, not a command name).
What Should Happen?
The ! keyword should be recognized as a bash reserved word and negate the exit status of the following pipeline, per POSIX shell grammar. Commands after ! should execute normally with all their side effects.
Error Messages/Logs
/bin/bash: line 1: !: command not found
Steps to Reproduce
All steps assume sandbox is enabled (default) and write to /tmp/claude/sandbox-test/ which is on the sandbox allowlist.
- Control — verify
touchworks without!:
``sh``
touch /tmp/claude/sandbox-test/ctrl.txt
ls /tmp/claude/sandbox-test/ctrl.txt # exists
- Bug —
if !prevents command execution:
``sh``
if ! touch /tmp/claude/sandbox-test/test.txt; then echo FAIL; else echo OK; fi
# stderr: /bin/bash: line 1: !: command not found
# stdout: OK (misleading — else branch runs because exit code is non-zero)
ls /tmp/claude/sandbox-test/test.txt # No such file or directory
- Confirm the command never runs:
``sh``
if ! bash -c 'echo INNER_EXEC >&2; touch /tmp/claude/sandbox-test/inner.txt'; then echo F; else echo OK; fi
# stderr: /bin/bash: line 1: !: command not found
# INNER_EXEC never appears — the inner command is never invoked
until(semantic equivalent) works fine:
``sh``
until touch /tmp/claude/sandbox-test/until.txt; do break; done
ls /tmp/claude/sandbox-test/until.txt # exists
- Disabling the sandbox confirms it as the cause:
``sh``
# With dangerouslyDisableSandbox: true
if ! touch /tmp/claude/sandbox-test/nosandbox.txt; then echo F; else echo OK; fi
ls /tmp/claude/sandbox-test/nosandbox.txt # exists
<details>
<summary>Full results table</summary>
| # | Command pattern | File created? | Notes |
|---|----------------|:---:|-------|
| A1 | touch file | Yes | Control |
| A2 | if ! touch file; then ...; fi | No | !: command not found |
| A3 | if touch file; then ...; fi | Yes | if without ! is fine |
| B1 | echo hello > file | Yes | Control |
| B2 | bash -c 'echo hello > file' | Yes | bash -c without ! is fine |
| B3 | if ! bash -c 'echo hello > file'; ... | No | File not created |
| C1 | cp src dest | Yes | Control |
| C2 | if ! cp src dest; ... | No | Dest not created |
| C3 | if ! mkdir dir; ... | No | Dir not created |
| H1 | ! touch file | No | Bare ! — same bug |
| H2 | if ! touch file; ...; fi; (trailing ;) | No | #16305 workaround does NOT help |
| H3 | touch file; rc=$?; if [ $rc -ne 0 ]; ... | Yes | $? capture workaround works |
| W1 | while ! touch file; do break; done | No | while ! also affected |
| W2 | until touch file; do break; done | Yes | until (semantic equivalent) works |
| D1 | if ! touch wt/tmp/file; ... (worktree) | No | Not specific to /tmp/claude/ |
| S1 | if ! touch file; ... (sandbox OFF) | Yes | Sandbox confirmed as cause |
| S2 | ! touch file (sandbox OFF) | Yes | Works without sandbox |
</details>
Claude Model
Opus
Is this a regression?
I don't know
Last Working Version
_No response_
Claude Code Version
2.1.34
Platform
Anthropic API
Operating System
Linux (Arch Linux, kernel 6.18.7-arch1-1)
Terminal/Shell
Alacritty / bash 5.3.9(1)-release
Additional Information
Workarounds
Capture $? instead of using !:
# Instead of: if ! some_command; then handle_failure; fi
some_command; rc=$?
if [ $rc -ne 0 ]; then handle_failure; fi
Use until instead of while !:
# Instead of: while ! some_command; do sleep 1; done
until some_command; do sleep 1; done
Root cause hypothesis
The sandbox intercepts command execution and splits the command line in a way that treats ! as the command name (argv[0]) rather than recognizing it as a bash reserved word. Evidence:
!: command not foundcomes from bash's command-not-found handler, which should never fire for!since it's parsed as a keyword during syntax analysis, before command lookup.- The command after
!never runs (confirmed viaecho INNER_EXEC >&2test). untilworks butwhile !does not — these are semantically equivalent, but only the form with!is affected.&&and||are not affected.
This issue has 5 comments on GitHub. Read the full discussion on GitHub ↗