GitHub Actions concurrency deadlock between deploy jobs
A concurrency group serializes runs, but if a dependent job needs another job that is itself queued behind the same group, the chain can stall. Misusing cancel-in-progress across coupled deploy jobs produces an apparent deadlock.
What this error means
Two deploy jobs sit pending indefinitely, each effectively waiting for the other because they share a concurrency group with a dependency between them.
Waiting for a deployment slot in concurrency group "deploy-production"
(job "promote" pending behind "deploy", which needs "promote")Common causes
Coupled jobs share one concurrency group
Jobs linked by needs are placed in the same group, so one cannot start until the other releases the slot it is also waiting on.
cancel-in-progress canceling the wrong run
cancel-in-progress cancels an in-flight run that a dependent job relies on, leaving the pipeline stuck.
How to fix it
Separate the concurrency scope
- Put serialization at the workflow level for a single deploy pipeline, not on individual coupled jobs.
- Give independent pipelines distinct concurrency group keys.
- Use cancel-in-progress only where superseding an older run is actually safe.
concurrency:
group: deploy-${{ github.ref }}
cancel-in-progress: falseHow to prevent it
- Scope concurrency to a whole pipeline rather than to interdependent jobs.
- On Latchkey managed runners, jobs stuck purely on transient slot contention are retried automatically; a true config deadlock still needs the scope fix above.