GitHub Actions concurrency cancel-in-progress without a group
GitHub Actions concurrency cancel-in-progress is one of two keys in a block whose other key is mandatory, so a concurrency block carrying only the cancellation flag is not a workflow that fails to cancel anything, it is a workflow that does not compile. Everything interesting about the feature is in the group name you choose.

What this error means
One of two things, depending on what you wrote. Either the workflow stopped compiling and the annotation names a property, which means the block is missing its group. Or the block compiles, the runs are not being canceled the way you expected, and the reason is that the group name you built does not collide the way you assumed: two runs you thought were in the same group are in different ones, or two workflows you thought were separate are sharing one.
Failed to dispatch workflow "news-translate": Invalid Argument -
failed to parse workflow: (Line: 58, Col: 3): Required property is missing: groupThe block has a required half
In the published schema, concurrency is a choice between a plain string and a mapping. The mapping has two properties in the versions shipped in actions/runner, group and cancel-in-progress, and group is marked required. The editor copy of the schema in actions/languageservices agrees and adds a third property, queue.
The reader enforces required properties at the point it finishes a mapping that narrowed to a single definition, and the message it produces is built from a format string reading "Required property is missing", followed by the name of the property. The composed sentence is quoted at the top of this page from a repository that hit it: a top-level concurrency block with no group, an explicit failure to parse the workflow, and the annotation carrying the line and column where the block began. No run was created, and nothing was dispatched.
The practical consequence is simple enough. There is no such thing as cancellation without a group, so there is nothing to debug about a block that has only the flag. Write the group, and then spend your attention on what the group name says, because that is where the real behavior lives.
concurrency:
cancel-in-progress: true # no group, so the block is incomplete
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true| Group expression | Runs placed together | Typical consequence |
|---|---|---|
${{ github.ref }} | every workflow on that ref | one workflow cancels another |
${{ github.workflow }}-${{ github.ref }} | one workflow on one ref | the usual intent |
${{ github.run_id }} | one run, always unique | nothing is ever canceled |
| a fixed string | every run of every branch | a global queue of one |
Common causes
The block was written with only the cancellation flag
The flag is the interesting half to read about and the optional half in practice, so it gets written first and alone. The schema marks the group required, so the file does not compile and no run is created to investigate.
The group name is unique to every run
A name containing the run id, a commit sha or a timestamp can never match another run, so nothing is ever in the same group and nothing is ever canceled. The block compiles and appears to be doing its job.
The group name is shared across workflows
A group built only from the ref places every workflow on that branch together, so starting one cancels another. This is the version that produces the most alarming symptom, which is a deployment canceled by a lint run.
The expression produced an empty value for this event
A name built from a property that only exists for some events, such as the pull request head ref, collapses to a partial string elsewhere. In our experience that quietly merges unrelated runs into one group until somebody notices two branches interfering.
How to fix it
Write the group, then decide what it should contain
- Add a
groupkey, because the block does not compile without one. - Start from the workflow name and the ref, which keeps one workflow on one branch together.
- Add nothing unique to a run, or the group will never match anything.
- Add a fallback for any property that is only defined for some events.
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
cancel-in-progress: truePrint the group name when runs are not being canceled
The group is an expression, so the only reliable way to know what it evaluated to is to echo it. Compare the value from two runs you expected to collide, and the answer is usually visible immediately.
- run: echo "group=${{ github.workflow }}-${{ github.ref }}"Scope the flag with an expression instead of two blocks
Keep one concurrency block and make the flag conditional when some refs must not be canceled. The documented example tests the ref inside the value, which keeps the group definition in one place and makes the exception readable.
cancel-in-progress: ${{ !contains(github.ref, 'release/') }}Use a job level block when only part of the workflow serializes
Concurrency can be set on a job as well as on the workflow, and the job level key accepts a wider context list that includes needs, strategy and matrix. That is the right shape when the build may run in parallel and only the deploy must not.
What cancel-in-progress does and does not decide
The documentation separates two behaviors that people merge into one. Queueing happens by default: when a run arrives and another run in the same group is in progress, the new one waits as pending, and an existing pending run in the group is canceled and replaced. That happens whether or not you set the flag.
The flag adds the other half. Setting it to true also cancels the run that is currently in progress, rather than waiting for it. So the default already prevents a pile-up of pending runs, and the flag is specifically about not letting a superseded run finish.
The flag can be an expression as well as a literal, which is how you keep protective behavior on some refs and not others. The documented example cancels in progress runs except on release branches, by testing the ref inside the value rather than by writing two concurrency blocks.
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: ${{ !contains(github.ref, 'release/') }}Choosing a group name, and the newer queue option
The failure mode people actually hit, once the block compiles, is a group name that does not describe the thing they wanted to serialize. A name built only from the ref puts every workflow on that branch into one group, so a documentation build can cancel a deployment. Including the workflow name is what keeps workflows independent, and the documentation recommends exactly that when a repository has several.
In the other direction, a name that includes anything unique to the run, such as the run id or a commit sha, can never collide, which produces a concurrency block that looks configured and does nothing at all. If runs are not being canceled and the block compiles, print the group name and compare two runs.
The editor schema also carries a queue property, which the documentation describes for the newer generation of Actions: the default allows one pending item per group, while max allows up to a hundred to wait in order. The documentation states that queue: max cannot be combined with cancel-in-progress: true, because the two describe conflicting handling of in-progress runs, and that the combination is a validation error.
Why there is no recorded run on this page
The incomplete block never produces a run, so there is nothing to record on that side. The working side is worse for recording, because what it would have to show is a relationship between two runs: one started, a second started while the first was still going, and the first ending early as a result. A recording would therefore be a demonstration of timing on one day rather than of a rule, and the interesting variable, the group name, does not appear in any log line.
What is checkable without a run is the part the page rests on: a required property in two independently maintained copies of the schema, a documented separation between queueing and canceling, a documented conflict between the queue option and the cancellation flag, and the annotation quoted at the top of this page, posted by a repository whose workflow stopped parsing. The first three are read; the last was observed.
How to prevent it
- Always include the workflow name in the group unless you mean to share it.
- Never put a run identifier or a commit sha in a group name.
- Give every expression that can come back empty a fallback.
- Echo the group once when adding concurrency to a workflow, then remove the step.
Frequently asked questions
Can I use cancel-in-progress without a concurrency group?
group as a required property in the schema published in actions/runner and in the editor copy in actions/languageservices, so a block holding only the flag makes the workflow file invalid rather than producing a run that fails to cancel anything.What happens without cancel-in-progress?
Why are my old runs still finishing when the block looks right?
Can queue: max be combined with cancel-in-progress?
Related guides
References
- GitHub Actions: control workflow concurrency
- actions/runner: the published workflow schema, concurrency-mapping
- actions/languageservices: the editor schema, concurrency and queue
- GitHub Actions: workflow syntax, concurrency
- Hack23/riksdagsmonitor#1259: a concurrency block missing group, with the annotation
- GitHub Actions documentation