Skip to content
Latchkey

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.

github-actions
concurrency:
  group: ${{ github.head_ref }}   # empty on push events -> all pushes share one group
  cancel-in-progress: true

Common 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

  1. Prefix the group with the workflow name and use a context value that is always set.
  2. Fall back to run_id when no better key exists.
.github/workflows/ci.yml
concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true

Default the dynamic part

  1. Use an expression that falls back when the primary value is empty.
  2. For example head_ref or ref so PRs and pushes both get a meaningful group.
.github/workflows/ci.yml
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.

Related guides

Tired of flaky CI? Latchkey auto-heals failed jobs and retries them for you. Start free →