Bash sandbox (bubblewrap) corrupts `!` to `\!` in commands, making the sandbox unusable for agentic workflows

Status Fixed / completed
Reported on v2.1.150
Maintainer reply None cached
Activity 5 comments · opened May 31, 2026 · closed Aug 17, 2026

Summary

Enabling the Bash sandbox on Linux corrupts ! to \! in every Bash tool command. The bubblewrap wrapping path quotes commands with a routine that escapes !, but Claude runs commands through bash -c (non-interactive, history expansion off), so the backslash is never removed and reaches the program as a literal. This breaks any command containing !: jq filters with !=, git commit -m 'fix!', conventional-commit feat!: titles, test assertions, passwords. The result is that the sandbox cannot be turned on for real agentic work, even though it otherwise functions correctly.

The same commands are correct with the sandbox off. The corruption is specific to the sandbox (bubblewrap) command path.

Reproduction

Linux with bubblewrap installed:

mkdir -p /tmp/cc-repro/.claude && cd /tmp/cc-repro
cat > .claude/settings.json <<'JSON'
{
  "sandbox": { "enabled": true, "failIfUnavailable": true },
  "permissions": { "defaultMode": "dontAsk", "allow": ["Bash"] }
}
JSON

claude -p "Run this exact bash command, one tool call, no edits: printf '%s\n' 'alpha!beta' > out.txt" \
  --allowedTools Bash --permission-mode dontAsk

cat out.txt

Expected out.txt:

alpha!beta

Actual out.txt:

alpha\!beta

The real-world case that surfaces it constantly is jq:

claude -p "Run this exact bash command: echo '[1,2,3]' | jq -c 'map(select(. != 2))'" \
  --allowedTools Bash --permission-mode dontAsk

Expected [1,3]. Actual:

jq: error: syntax error, unexpected INVALID_CHARACTER (Unix shell quoting issues?) at <top-level>, line 1:
map(select(. \!= 2))

Set sandbox.enabled to false and rerun: both commands are correct. CLAUDE_CODE_SUBPROCESS_ENV_SCRUB=1 triggers the same corruption, since it forces the bubblewrap path on (#50167).

Affected versions

Reproduced on claude 2.1.150, 2.1.158 (npm latest), and 2.1.159 (npm next), installed via https://claude.ai/install.sh, on Ubuntu 24.04 with bubblewrap. Linux only: the macOS Seatbelt sandbox uses a different command path and is not affected.

Root cause

The bundled shell-quote quote() used on the bubblewrap path escapes ! inside its double-quote-wrapping branch (the branch taken when a token contains both a single quote and whitespace, which is common in jq filters). That escaping is correct for an interactive shell, where ! triggers history expansion inside "...". Claude runs commands through bash -c, which is non-interactive and has histexpand off by default, so ! has no special meaning and the inserted \ is passed through literally.

The non-bubblewrap path uses a different, correct quoting routine, which is why the bug only appears once the sandbox is on.

Why this matters

This is reported here as a sandbox blocker rather than a generic quoting annoyance. The sandbox is the recommended way to let an agent run Bash autonomously, but an agent that cannot run jq, write a commit message, or handle a ! in any argument is not viable for autonomous use. The escaping silently corrupts data rather than failing loudly, so it can write \! into commit messages, file contents, and downstream tools before anyone notices.

Suggested fix

Drop ! from the escape set on the bubblewrap quoting path. Since commands run via bash -c (non-interactive), history expansion is off and ! needs no escaping:

- q.replace(/(["\\$`!])/g, "\\$1")
+ q.replace(/(["\\$`])/g, "\\$1")

Prior reports

This has been filed several times and closed as not-planned or by inactivity, each framed as a quoting bug rather than a sandbox blocker:

  • #35701 — shell-quote escapes ! to \! in non-interactive shells, corrupting command data
  • #32864 — Bash tool escapes ! to \! when adjacent to whitespace
  • #29210 — Bash tool silently converts ! (0x21) to \!, corrupts arguments to executables
  • #38921 — invalid Python syntax from escaped characters
  • #23740 — breaks jq and other tools

Related sandbox behavior: #50167 (CLAUDE_CODE_SUBPROCESS_ENV_SCRUB forces the sandbox on), #35986 (sandbox.enabled: false still wraps with bubblewrap).

_This was written by Claude Code on behalf of Maximilian Roos_

View original on GitHub ↗

4 Comments

lyphyser · 2 months ago

Can you fix this ASAP?

It's a critical bug that corrupts bash tool calls and it's been there at least for 3 weeks

akovalenko · 2 months ago

Claude code 2.1.195, bug still there, very annoying. Thank you.

akovalenko · 2 months ago

Until it's fixed upstream, the workaround that's worked best for us isn't trying
to escape around the corruption — it's making the failure loud instead of
silent
. We added a PreToolUse hook that denies any sandboxed Bash command
containing a bang, with a reason telling the agent to write the script to a file
and run the file (python f.py / awk -f f.awk / bash f.sh) — which keeps the
bang off the command line entirely. Sketch:

# PreToolUse hook, placed after your own allow rules
if [ "$tool" = "Bash" ] && [ "$sandboxed" = "true" ]; then
  cmd="$(jq -r '.tool_input.command // ""' <<<"$input")"
  case "$cmd" in
    *'!'*) echo '{"decision":"deny","reason":"sandboxed Bash corrupts the bang char (#64301); write the script to a file and run the file"}'; exit 0 ;;
  esac
fi
akovalenko · 1 month ago

Claude code 2.1.218, bug still present.

Showing cached comments. Read the full discussion on GitHub ↗