Quoted heredoc (<<'EOF') prevents intentional command substitution in nested contexts

Resolved 💬 1 comment Opened May 8, 2026 by lmello Closed Jun 5, 2026

Bug Description

When Claude Code constructs shell commands that use the $(cat <<'EOF' ... EOF) pattern (as recommended in its system prompt for git commits), it always uses single-quoted heredoc delimiters. This prevents intentional command substitution inside the heredoc body.

If you ask Claude to embed file contents into a command using $(cat file) within a heredoc, it produces:

gh pr comment --body "$(cat <<'EOF'
## Title

$(cat /path/to/some/file.txt)
EOF
)"

The inner $(cat /path/to/some/file.txt) is printed literally instead of being expanded, because <<'EOF' disables all substitution.

Expected Behavior

Claude should recognize when command substitution inside a heredoc is intentional and either:

  1. Use an unquoted delimiter (<<EOF) when nested expansion is desired
  2. Pre-read the file into a variable and interpolate it
  3. Use a different approach (e.g., --body-file flag or pipe from stdin)

Reproduction Steps

  1. Have a file at /tmp/example.txt with content Hello World
  2. Ask Claude: "Post a GitHub PR comment that includes the contents of /tmp/example.txt inside a details block"
  3. Claude generates:
gh pr comment 123 --body "$(cat <<'EOF'
<details>
$(cat /tmp/example.txt)
</details>
EOF
)"
  1. The PR comment shows the literal text $(cat /tmp/example.txt) instead of Hello World

Root Cause

This appears to be an over-correction from the heredoc injection vulnerability fix (#52408). The security fix correctly defaults to <<'EOF' to prevent unintended expansion of user content. However, it applies this rule even when Claude itself is constructing the command and intentionally wants substitution to occur.

Suggested Fix

When Claude is generating a command that needs to embed file contents:

  • Read the file content into a variable first: CONTENT=$(cat file); gh pr comment --body "$CONTENT"
  • Or use stdin: cat file | gh pr comment --body-file -
  • Or detect that the user asked for file embedding and use unquoted heredoc with appropriate escaping

The key distinction: user-provided text should use <<'EOF' (safe). Claude-generated commands that intentionally embed file contents should use a pattern that allows expansion.

Environment

  • Claude Code CLI version: 2.1.136
  • macOS Darwin 24.6.0 / zsh
  • Model: claude-sonnet-4-6
  • Affects any command where file contents need to be embedded in a heredoc argument

View original on GitHub ↗

This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗