[BUG] Bash tool escapes `!` in file paths

Resolved 💬 3 comments Opened Mar 28, 2026 by stefansimik Closed Apr 1, 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 Bash tool automatically escapes ! to \! before passing commands to the shell. This happens at the tool level, not in the shell itself, so no quoting strategy (single quotes, set +H, manual escaping) can prevent it. Any file or directory with ! in its name is inaccessible via the Bash tool.

What Should Happen?

The Bash tool should pass ! through to the shell unmodified. Shell-level quoting (single quotes) is sufficient to handle zsh history expansion - the tool should not add its own escaping on top.

Error Messages/Logs

$ ls '/tmp/test-!-dir/'
ls: /tmp/test-\!-dir/: No such file or directory


Note the `\!` - the tool inserted a literal backslash before `!`. All quoting strategies produce the same result:


ls '/tmp/test-!-dir/'           # → \! in error
ls "/tmp/test-!-dir/"           # → \! in error
ls /tmp/test-\!-dir/            # → \\! in error
set +H && ls '/tmp/test-!-dir/' # → \! in error

Steps to Reproduce

  1. In a normal terminal, create a test directory:

```bash
mkdir '/tmp/test-!-dir'
touch '/tmp/test-!-dir/file.txt'

2. In Claude Code, ask Claude to run: `ls '/tmp/test-!-dir/'`
3. The command fails with `No such file or directory`, showing `\!` in the path

Workaround - use Python to avoid `!` in command arguments:
```bash
python3 << 'EOF'
import os
print(os.listdir('/tmp/test-!-dir'))
EOF

Claude Model

Opus

Is this a regression?

I don't know

Last Working Version

_No response_

Claude Code Version

2.1.86

Platform

Anthropic API

Operating System

macOS

Terminal/Shell

iTerm2

Additional Information

Discovered while trying to mv a directory named !image_generation_workflow. Every direct shell command (mv, git mv, ls) failed. The only workaround was delegating to Python via heredoc, which bypasses the tool's escaping layer.

View original on GitHub ↗

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