TIL: GitHub Actions concurrency.cancel-in-progress
Was watching our CI queue pile up on a busy PR with many force-pushes. Each force-push triggered a new build, old builds kept running. Wasted runner-minutes everywhere.
Fix:
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
Put that at the top of the workflow. When a new run for the same branch starts, the previous in-progress run gets cancelled. Branch here means “per PR” or “per branch.”
For main-branch runs, you probably don’t want cancellation — you want every commit to main to be fully tested. Scope accordingly:
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: ${{ github.ref != 'refs/heads/main' }}
For a deployment workflow, the pattern I use is slightly different: don’t cancel, just queue. A new deploy waits for the previous to finish:
concurrency:
group: deploy-${{ github.ref }}
# no cancel-in-progress; they queue
This prevents concurrent deploys from stepping on each other without killing one in the middle (which might leave things in a weird state).
Saved us ~20% of runner-minutes immediately. One of the cheaper wins I’ve done in CI.