Quoted heredoc (<<'EOF') prevents intentional command substitution in nested contexts
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:
- Use an unquoted delimiter (
<<EOF) when nested expansion is desired - Pre-read the file into a variable and interpolate it
- Use a different approach (e.g.,
--body-fileflag or pipe from stdin)
Reproduction Steps
- Have a file at
/tmp/example.txtwith contentHello World - Ask Claude: "Post a GitHub PR comment that includes the contents of /tmp/example.txt inside a details block"
- Claude generates:
gh pr comment 123 --body "$(cat <<'EOF'
<details>
$(cat /tmp/example.txt)
</details>
EOF
)"
- The PR comment shows the literal text
$(cat /tmp/example.txt)instead ofHello 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
This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗