claude-code - 💡(How to fix) Fix Bash sandbox (bubblewrap) corrupts `!` to `\!` in commands, making the sandbox unusable for agentic workflows

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…

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.

Error Message

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

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.

Code Example

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

---

alpha!beta

---

alpha\!beta

---

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

---

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

---

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

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

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING

claude-code - 💡(How to fix) Fix Bash sandbox (bubblewrap) corrupts `!` to `\!` in commands, making the sandbox unusable for agentic workflows