GitHub Actions concurrency group expression evaluates to empty
A concurrency group is a string; runs sharing a group serialize and can cancel each other. If the expression that builds the group resolves to an empty value, every run lands in the same empty-named group and they cancel one another unexpectedly.
What this error means
Unrelated runs cancel each other because they all resolve to the same (empty) concurrency group.
concurrency:
group: ${{ github.head_ref }} # empty on push events -> all pushes share one group
cancel-in-progress: trueCommon causes
Context value empty for this event
head_ref is empty on push; using it alone makes every push share an empty group.
No static prefix to disambiguate
Without a static segment, an empty dynamic part yields a single shared group.
How to fix it
Combine a static prefix with a non-empty selector
- Prefix the group with the workflow name and use a context value that is always set.
- Fall back to run_id when no better key exists.
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: trueDefault the dynamic part
- Use an expression that falls back when the primary value is empty.
- For example head_ref or ref so PRs and pushes both get a meaningful group.
group: ${{ github.workflow }}-${{ github.head_ref || github.ref }}How to prevent it
- Always include a static prefix and an always-set context value in concurrency groups.
- Avoid relying solely on event-specific fields like head_ref.