How to Use Job-Level Concurrency to Control CI
Without concurrency control, every push spins up a fresh run while stale ones keep burning minutes. Concurrency groups cut the waste.
GitHub Actions concurrency groups cap how many runs of a workflow execute at once for a given key. The two big uses are canceling superseded PR runs and serializing deploys so they never overlap.
1. Cancel superseded PR runs
Group by ref and cancel in-progress runs so only the latest commit on a branch is tested.
concurrency:
group: ci-${{ github.ref }}
cancel-in-progress: true2. Serialize deploys, do not cancel them
For deploys, queue instead of cancel so an in-flight deploy finishes before the next starts.
concurrency:
group: deploy-production
cancel-in-progress: false3. Choose the group key deliberately
The group key defines the scope. Per-ref cancels within a branch; a fixed key serializes everything globally. Pick the narrowest scope that prevents the conflict you care about.
4. Concurrency saves minutes, right-sizing saves dollars
Cancelling stale runs stops wasted minutes; running the surviving job on a cheaper, right-sized Latchkey runner cuts the per-minute cost too. The two stack.
Key takeaways
- cancel-in-progress: true kills superseded PR runs to save minutes.
- cancel-in-progress: false serializes deploys so they never overlap.
- The group key sets the scope; pick the narrowest that prevents the conflict.