Each job gets a fresh runner; jobs without needs start in parallel. Steps run in order on one machine, and a failing step skips those after it (unless their if says otherwise, Conditional Steps and Jobs). The shells job shows a trap:
shells:
runs-on: ubuntu-24.04
steps:
- name: Default shell
run: false | true; echo "pipeline status $?"
- name: Explicit bash adds pipefail
shell: bash
continue-on-error: true
run: false | true; echo "not printed"
- name: Python as a shell
shell: python
run: import platform; print("Python", platform.python_version())Its log in the run for pull request #16, filtered to each step's command, shell and result:
##[group]Run false | true; echo "pipeline status $?"
shell: /usr/bin/bash -e {0}
pipeline status 0
##[group]Run false | true; echo "not printed"
shell: /usr/bin/bash --noprofile --norc -e -o pipefail {0}
##[error]Process completed with exit code 1.
##[group]Run import platform; print("Python", platform.python_version())
shell: /usr/bin/python {0}
Python 3.12.3With no shell, a Linux step runs bash -e on a temporary script ({0}), and a pipeline reports only its last command, so failing-test | tee log.txt passes. shell: bash adds -o pipefail. Windows defaults to pwsh. continue-on-error kept the job green. Set defaults: run: shell: bash on a workflow so every step gets pipefail.