[BUG] Sandboxed bash does not recognize `!` and gives "command not found"

Resolved 💬 5 comments Opened Feb 8, 2026 by jonhoo Closed Mar 8, 2026

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.

  1. Control — verify touch works without !:

``sh
touch /tmp/claude/sandbox-test/ctrl.txt
ls /tmp/claude/sandbox-test/ctrl.txt # exists
``

  1. 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
``

  1. 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
``

  1. 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
``

  1. 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:

  1. !: command not found comes 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.
  2. The command after ! never runs (confirmed via echo INNER_EXEC >&2 test).
  3. until works but while ! does not — these are semantically equivalent, but only the form with ! is affected.
  4. && and || are not affected.

View original on GitHub ↗

This issue has 5 comments on GitHub. Read the full discussion on GitHub ↗