How to Use set -euo pipefail in GitHub Actions Scripts
set -euo pipefail turns silent bugs into loud failures: exit on error, error on unset vars, and fail a pipeline if any stage fails.
Start each multi-line bash step (or your script file) with set -euo pipefail. -e exits on error, -u errors on unset variables, and -o pipefail propagates failures through pipes.
Steps
- Put
set -euo pipefailas the first line of the script orrunblock. - Quote variable expansions so word-splitting does not defeat
-u. - Use
${VAR:-default}where an unset value is legitimately allowed.
Workflow
steps:
- run: |
set -euo pipefail
curl -fsSL https://example.com/data.json | jq '.version' > version.txt
test -s version.txtGotchas
- GitHub bash already sets
-eandpipefail, but not-u; add it yourself to catch typos in variable names. - With
-e, a command whose non-zero exit is expected needs|| trueso it does not abort the step.
Verify it actually works
A workflow that runs is not a workflow that works. Confirm the behaviour on a real event rather than on a manual dispatch, because trigger conditions, permissions, and context values all differ between the two.
# 1. validate the file before pushing
docker run --rm -v "$(pwd):/repo" --workdir /repo rhysd/actionlint:latest -color
# 2. trigger the real event, not workflow_dispatch
git commit --allow-empty -m "ci: verify trigger" && git push
# 3. watch it and read the conclusion, not just the colour
gh run watch
gh run view --log-failedWhat usually goes wrong first
- The workflow file must exist on the default branch before scheduled or dispatch triggers appear at all.
GITHUB_TOKENpermissions default to read-only in many organisations. Declare apermissions:block listing every scope the job needs.- Fork pull requests get a read-only token and no access to secrets, regardless of workflow configuration.
actions/checkoutgives you depth 1 on a detached HEAD, so anything needing history or a branch name needsfetch-depth: 0.