claude-code - 💡(How to fix) Fix Quoted heredoc (<<'EOF') prevents intentional command substitution in nested contexts

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…

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.

Code Example

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

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

---

gh pr comment 123 --body "$(cat <<'EOF'
<details>
$(cat /tmp/example.txt)
</details>
EOF
)"
RAW_BUFFERClick to expand / collapse

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

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 Quoted heredoc (<<'EOF') prevents intentional command substitution in nested contexts