Stop paying for superseded GitHub Actions runs
Push three commits to a pull request in ten minutes and you start three full runs. The first two are already obsolete, but you paid for them.
Why it happens
By default every workflow run is independent. A newer commit does not cancel the run for the older one, so a busy pull request bills for each intermediate state — and reviewers only ever look at the last one.
The change
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
Add it at the top level of the workflow, next to on: and jobs:. Two things matter here:
groupis keyed ongithub.ref, so runs for the same branch or pull request share a group and supersede each other.cancel-in-progressis bound to the event, so a push to a branch is never cancelled. Only an older commit on the same pull request is.
That expression is the difference between a safe change and a destructive one. A blanket cancel-in-progress: true also cancels pushes, which is fine for tests and wrong for anything that publishes or deploys.
How much it saves
It depends on how often you push to open pull requests. If a quarter of your pull-request runs are superseded before finishing, you stop paying for a quarter of them. Cancelled runs bill only for the minutes they actually consumed, so the saving is real even though the run was started.
When not to do this
- Do not add it to a workflow that deploys, releases or publishes — a cancelled release is not a saving.
- Be careful with
pull_request_targetand any workflow holding a privileged token. - If your checks are required for branch protection, confirm that the latest run still completes normally. It does: only superseded runs are cancelled.
CIPatch abstains from all three cases, so it will not propose this in a release pipeline.
CIPatch checks which of your workflows run on pull requests and whether a safe concurrency block is missing. The scan is read-only, needs no account for the first repository, and changes nothing.