GitHub Actions success() and failure() both false when the run is cancelled
success() is true only when prior steps succeeded; failure() is true only when one failed. A cancelled run is neither, so a step guarded by success() || failure() is skipped - cleanup that should always run does not.
What this error means
A cleanup or teardown step is skipped when a job is cancelled, even though it should run regardless of outcome.
github-actions
- name: Teardown
if: ${{ success() || failure() }} # both false on cancel -> step skipped
run: ./teardown.shCommon causes
cancelled() is a distinct state
A cancel is neither success nor failure; cancelled() must be checked separately or always() used.
Misuse of success() || failure() as a catch-all
That combination excludes cancellation; it is not equivalent to always().
How to fix it
Use always() for unconditional cleanup
- Replace the success/failure combination with always() so the step runs in every outcome including cancellation.
- Inside the step, branch on the specific status if needed.
.github/workflows/ci.yml
- name: Teardown
if: ${{ always() }}
run: ./teardown.shExplicitly include cancelled()
- If you do not want always(), add || cancelled() to cover the cancel state.
- Combine the three status functions for the exact set you intend.
How to prevent it
- Use always() for teardown that must run on cancellation.
- Remember cancelled() is separate from success() and failure().
Related guides
GitHub Actions if: always() Runs a Step Even After CancellationFix GitHub Actions if: conditions using always(), success(), failure(), and cancelled() - always() runs even…
GitHub Actions Deploy Canceled Mid-Run by a Concurrency GroupStop GitHub Actions deploys being canceled mid-run - a shared concurrency group with cancel-in-progress abort…
GitHub Actions conditional job skipped but required check stays pending in branch protectionFix a PR that cannot merge because a skipped conditional job leaves its required status check unfulfilled.