GitHub Actions multiline run script fails on the block scalar
A GitHub Actions multiline run script fails most often because the block scalar chosen for it folds line breaks into spaces, which turns a list of commands into one very long command. The shell is not misbehaving and the runner is not mangling anything: by the time either of them sees the script, YAML has already decided what the text is.

What this error means
A run step with several lines under it behaves as though the lines were joined, or loses a line, or fails with a shell syntax error that quotes a fragment spanning what you thought were two separate commands. A common variant is a command that appears to receive extra arguments it never had, which is the next line having arrived as part of it. Another is a heredoc that never terminates, because the terminator ended up on the same line as its content. Nothing in the log names YAML, so the shell takes the blame.
run: >
echo one
echo two
# the shell receives: echo one echo twoThree rules decide what the shell receives
The first rule is the choice of style. A literal block scalar keeps every line break exactly as written, which is what a script wants. A folded block scalar replaces a single line break between two ordinary lines with a single space, which is what a paragraph of prose wants. There is no third option and no default that guesses: writing neither indicator means the value is an ordinary scalar, not a block at all.
The second rule is the one that surprises people who already know the first. In a folded block, lines that are indented further than the block indentation are not folded. Their line breaks are preserved. So a folded block containing an indented continuation produces a mixture, some lines joined and some not, which is far harder to diagnose than everything being joined.
The third rule is chomping, which decides what happens to the line breaks at the end. The default keeps a single trailing newline. An added minus strips them all, and an added plus keeps every trailing blank line. This rarely breaks a script outright, and it matters when a value is being written to a file or compared as a string, where an unexpected trailing newline changes the answer.
None of this is specific to GitHub. These are the YAML block scalar rules, and any workflow file is subject to them before any part of Actions reads a single key.
| What you write | What the line breaks become | Use it for |
|---|---|---|
run: | | kept, every one of them | scripts, always |
run: > | a single break becomes a space | prose, never scripts |
run: > with indented lines | those lines keep their breaks | nothing deliberately |
run: |- | kept, with the trailing ones stripped | a value with no trailing newline |
Common causes
The folded style was used for a script
The commonest cause. A single line break between two ordinary lines becomes a space, so consecutive commands are concatenated into one, and the failure looks like a command receiving arguments it never had.
A folded block contained more indented lines
Those lines are not folded, so their breaks survive. The result is a script where some joins happened and some did not, which is much harder to read back than a script that was joined uniformly.
A line was indented less than the block indentation
That ends the block. What follows is parsed as something else at the parent level, which is either a confusing key or a syntax error depending on the text.
A heredoc terminator was folded onto another line
A heredoc needs its terminator alone on a line. In our experience this is the version that takes longest to diagnose, because the error is about an unterminated document rather than about the line that moved.
The first command failed and the rest never ran
Not a YAML problem at all, and it presents identically at a glance. A missing line in the output is a line that did not run, and the reason is usually above it.
How to fix it
Use the literal style for every script
Replace the folded indicator with the literal one. There is no situation inside a run key where folding is what you want, so this can be a rule rather than a judgement.
- run: |
echo one
echo two
./build.shPrint the parsed value to see the real script
- Load the workflow file with any YAML parser.
- Print the value of the run key for the step in question.
- Compare what comes out against what you believed you wrote.
python3 -c "import sys,yaml; d=yaml.safe_load(open('.github/workflows/ci.yml')); print(repr(d['jobs']['build']['steps'][0]['run']))"Check the indentation of every line in the block
Every line must be indented at least as far as the first non empty one. Where leading whitespace is meaningful, pin the block indentation with an explicit indicator rather than relying on what the first line happens to be.
Move anything long into a script file
A file in the repository gets highlighting, linting and a proper review. The workflow step becomes a single line, which removes this whole category of problem rather than avoiding it.
- run: ./scripts/release.sh
env:
VERSION: ${{ github.ref_name }}The cases where folding is not the culprit
Indentation inside the block is the next thing to check. Every line of a block scalar must be indented at least as far as the block indentation established by its first non empty line. A line that is indented less than that ends the block, so the rest of what you thought was your script becomes a sibling key or a syntax error, depending on what it looks like.
A related trap is a first line that begins with a space. Since the first non empty line sets the indentation, a script whose first line is deliberately indented shifts the whole block, and the leading space you wanted disappears. YAML provides an explicit indentation indicator for exactly this, a digit after the style character, which pins the indentation rather than inferring it.
The last one is not a YAML problem at all and gets blamed on YAML constantly. A multi line script runs under a shell that stops at the first failing command, so a script whose second line never runs may have had its first line fail rather than its second line vanish. Reading the log for the output of line one settles it in a second, and our page on a run step exiting with status one covers that behavior.
# the block indentation is set by the first line, so this loses the leading spaces
run: |
indented line
normal line
# pin it explicitly with an indentation indicator
run: |2
this line keeps two of its leading spacesWriting multi line scripts that stay readable
Use the literal style for every script, without exception. The folded style has no use inside a run key, and treating the choice as a decision at all is how the wrong one gets made. If a line is genuinely too long, break it with a backslash, which is the shell own continuation and survives the literal style unchanged.
Keep heredocs away from folded blocks entirely. A heredoc depends on its terminator being alone on a line, and folding is precisely the operation that puts it somewhere else. Inside a literal block a heredoc behaves normally.
When a script grows past a handful of lines, move it into a file in the repository and call it. A file gets syntax highlighting, a shell linter, and review that reads it as code rather than as configuration. The step then becomes one line, and none of the rules on this page can affect it.
- run: |
set -u
npm ci
npm run build \
--workspace=app
- run: ./scripts/build.shWhy there is no recorded run on this page
The transformation described here is complete before the runner is involved, so a recorded run would show the consequence and not the cause. The interesting artifact is the script text after YAML has finished with it, and that text does not appear in the log: what appears is whatever the shell made of it.
The rules themselves come from the YAML specification, which is a better source than any run because it is what every parser implements. Checking your own file against them is also something you can do locally in seconds, by loading the file with any YAML parser and printing the value of the run key, which is more direct evidence than any recording we could make.
There is nothing repairable here either. A script that was folded into one line is a script the file asked for, and the runner has no way to know which spaces used to be line breaks.
How to prevent it
- Allow only the literal block style under a run key, as a review rule.
- Move any script longer than about ten lines into a file in the repository.
- Never put a heredoc inside a folded block.
- When whitespace matters, pin the block indentation explicitly.
Frequently asked questions
What is the difference between the two block scalar styles in a run step?
Why did only some of my lines get joined?
Why does my heredoc never terminate?
How do I keep leading spaces in a multiline run?
My second command never ran. Is that folding?
Related guides
References
- YAML 1.2.2 specification: block scalar styles, folding and chomping
- GitHub Actions: workflow syntax, jobs.<job_id>.steps[*].run
- actions/runner: ScriptHandler.cs, where the evaluated script is written to a file
- actions/runner: YamlObjectReader.cs, the YAML reader the workflow parser uses
- GitHub Actions documentation