claude-code icon indicating copy to clipboard operation
claude-code copied to clipboard

[BUG] Incorrect escaping inside backticks: `!` is escaped as `\!`

Open ers35 opened this issue 2 months ago • 9 comments

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?

I ask Claude Code:

Run this command: /bin/echo '`!`'

but it returns:

⏺ Bash(/bin/echo '`!`')
  ⎿  `\!`

What Should Happen?

Claude should return:

⏺ Bash(/bin/echo '`!`')
  ⎿  `!`

Steps to Reproduce

Ask Claude Code:

Run this command: /bin/echo '`!`'

Another example:

> Make POST request to `https://echo.zuplo.io/` with this JSON data: {"text":"`!=`"}

⏺ I'll make a POST request to that URL with the JSON data.

⏺ Bash(curl -X POST "https://echo.zuplo.io/" -H "Content-Type: application/json" -d '{"text":"`!=`"}')
  ⎿  {
       "url": "https://echo.zuplo.io/",
       "method": "POST",
       "body": "{\"text\":\"`\\!=`\"}",
     }

vs:

% curl -X POST "https://echo.zuplo.io/" -H "Content-Type: application/json" -d '{"text":"`!=`"}'
{
  "url": "https://echo.zuplo.io/",
  "method": "POST",
  "body": {
    "text": "`!=`"
  },
}

Claude Model

Sonnet (default)

Is this a regression?

I don't know

Last Working Version

No response

Claude Code Version

2.0.25

Platform

Anthropic API

Operating System

macOS

Terminal/Shell

Terminal.app (macOS)

Additional Information

No response

ers35 avatar Oct 23 '25 00:10 ers35

Found 2 possible duplicate issues:

  1. https://github.com/anthropics/claude-code/issues/2941
  2. https://github.com/anthropics/claude-code/issues/7387

This issue will be automatically closed as a duplicate in 3 days.

  • If your issue is a duplicate, please close it and 👍 the existing issue instead
  • To prevent auto-closure, add a comment or 👎 this comment

🤖 Generated with Claude Code

github-actions[bot] avatar Oct 23 '25 00:10 github-actions[bot]

Is this a major problem? ! is a special character in the shell.

As a workaround you can use this:

! echo !
  ⎿  !

brandon-fryslie avatar Oct 23 '25 01:10 brandon-fryslie

Is this a major problem? ! is a special character in the shell.

As a workaround you can use this:

! echo ! ⎿  !

The real problem is Claude Code can't use the Bash tool to write files that have ``!\ in them. For example, generating markdown that references code with !=.

There are many contexts in the shell where backticks are valid. For example, wrapped in single quotes or a heredoc.

ers35 avatar Oct 23 '25 02:10 ers35

Ah good call. I hadn't considered the backticks since I was focusing on the bang. This does seem like an issue. I did a bit of testing in Claude.

> Print these characters using the bash tool: `!`

⏺ I'll print those characters using the Bash tool:

⏺ Bash(echo '`!`')
  ⎿  `\!`

⏺ The Bash tool printed: \!

  The exclamation mark gets escaped with a backslash in the output, which is expected behavior in bash when using single quotes with history expansion characters.

> why is that?

⏺ The backslash appears because of how bash handles history expansion and special characters:

  1. History Expansion: In bash, ! is a special character used for history expansion (like !! for the last command, !$ for the last argument, etc.)
  2. When You Run Locally: When you ran echo '!' in your terminal, you got ! without the backslash because your shell directly processed it. Inside single quotes, ! is treated literally in most contexts.
  3. When Claude Code Runs It: The Bash tool executes commands through a shell pipeline that may have different settings or processing layers. The backslash before ! is being added as an escape sequence
  somewhere in that execution chain - likely to prevent the shell from attempting history expansion.
  4. The Backticks: The backticks themselves are preserved because they're inside single quotes, which prevents command substitution.

  If you want to see the exact characters without any escaping, you could use:
  - printf '%s\n' '!' - printf is more predictable
  - cat << 'EOF' with a heredoc
  - Or just echo with double quotes: echo "\!"` (escaping the backticks)

  The behavior difference you're seeing between your local runs and the tool runs likely comes from different bash settings (like set +H to disable history expansion) or how the command execution environment is
   configured.

> OK, print `!` with each of those and we'll see what works

⏺ I'll test each method to see which one prints `!` correctly:

⏺ Bash(printf '%s\n' '`!`')
  ⎿  `\!`

⏺ Bash(cat << 'EOF'
      `!`…)
  ⎿  `!`

⏺ Bash(echo "\`!\`")
  ⎿  `!`

⏺ Great! Here are the results:

  1. printf '%s\n' '!' → `\!` ❌ (still escaped)
  2. cat << 'EOF' heredoc → `!` ✅ (perfect!)
  3. echo "\!"` → `!` ✅ (works!)

  Winners: Both the heredoc method and the double-quoted echo with escaped backticks work perfectly. The heredoc is probably the cleanest for multi-line content, while the double-quoted echo is simpler for
  single-line output.

  Interestingly, even printf with single quotes still shows the escaped !, which suggests the escaping is happening at a deeper level in how the Bash tool processes output, not just in the echo command itself.

I suppose as a workaround until this is fixed you could put in your CLAUDE.md either always use the Write tool when writing files and/or always use HEREDOCs when using the Bash tool to write files.

brandon-fryslie avatar Oct 23 '25 20:10 brandon-fryslie

@brandon-fryslie --

Is this a major problem? ! is a special character in the shell.

As a workaround you can use this:

! echo ! ⎿  !

It's only a special character in INTERACTIVE shells with history expansion enabled. Noninteractive shells do not treat ! as special, and even interactive shells don't treat it as special inside single quotes. Normal single quotes ('...' as opposed to the ANSI C-like quoting style $'...') don't require anything to be escaped -- only another single quote can be treated as non-literal in that context.

charles-dyfis-net avatar Dec 09 '25 21:12 charles-dyfis-net

(I hit this very regularly when jq is being used to filter JSON content -- the model can very easily want to run jq 'select(.foo != "bar")', and having that changed to \!= breaks the syntax).

charles-dyfis-net avatar Dec 09 '25 21:12 charles-dyfis-net