GitHub Actions Heredoc in a run: Step Breaks or Expands Variables
A heredoc inside a run: script behaves unexpectedly - variables expand when you wanted them literal (or vice versa), or the block never closes because the terminator is indented or misspelled.
What this error means
A heredoc writes the wrong content: $VAR is left literal when you wanted it expanded, expressions are substituted when you wanted them raw, or the step hangs/fails because the closing delimiter was not recognized.
- run: |
cat <<EOF > out.txt
version is $VERSION
EOF
# EOF (unquoted) expands $VERSION; <<'EOF' would keep it literalCommon causes
Quoted vs unquoted delimiter controls expansion
cat <<EOF expands $VAR and command substitutions; cat <<'EOF' keeps the body literal. Choosing the wrong one writes the wrong content.
Indented or mismatched terminator
The closing delimiter must match exactly and start at column zero unless you use <<-EOF with tabs. A space-indented EOF inside a YAML block scalar is not recognized, so the heredoc never ends.
How to fix it
Pick the delimiter form deliberately
Use an unquoted delimiter to expand variables, a single-quoted delimiter to keep the body literal.
- run: |
cat <<'EOF' > template.txt
keep $THIS and ${{ this }} literal
EOFKeep the terminator unambiguous
- Put the closing delimiter flush left within the run: block scalar, with no leading spaces.
- Use a unique token (e.g. EOF_TEMPLATE) so it cannot appear in the body.
- Remember GitHub still interpolates ${{ }} before the shell, so quote the delimiter to keep expressions literal too.
How to prevent it
- Choose quoted vs unquoted heredoc delimiters based on whether you want expansion.
- Keep the closing delimiter flush left and unique.
- Test multiline scripts locally in the same shell GitHub uses.