Skip to content

ci: add concurrency groups to cancel superseded PR runs - #54

Open
JustAGhosT wants to merge 1 commit into
masterfrom
ci/concurrency-groups
Open

ci: add concurrency groups to cancel superseded PR runs#54
JustAGhosT wants to merge 1 commit into
masterfrom
ci/concurrency-groups

Conversation

@JustAGhosT

@JustAGhosT JustAGhosT commented Aug 12, 2026

Copy link
Copy Markdown
Collaborator

Superseded runs currently burn to completion instead of cancelling. This repo billed 504 Actions minutes ($3.04) 1–12 Aug 2026.

Why

Measured in phoenixvc/mystira-workspace over the same period:

Workflow concurrency Supersessions Outcome
[CI] Validate PR none 72 ran to completion — 856 wasted wall-minutes, zero cancelled
[CI] Identity Product cancel-in-progress: true 38 cancelled within ~1 min each — 36 min total

Same churn, 24x the waste. Reference implementation: phoenixvc/mystira-workspace#3739.

What changed

CI — cancels on pull_request only (ci, lint, monorepo-ci, security, validate-templates, validate-version):

concurrency:
  group: <workflow-id>-${{ github.event.pull_request.number || github.ref }}
  cancel-in-progress: ${{ github.event_name == 'pull_request' }}

Six workflows all trigger on pull_request to master/develop, so a rapid push sequence currently starts six duplicate stacks and lets every superseded one finish.

Group only, NO cancel-in-progress — runs serialise instead of being interrupted:

Workflow Why it must not cancel
deploy-autopr-engine, deploy-website Interrupting a deploy can leave it half-applied
destroy-infra Interrupting a teardown can strand Terraform state
release, release-desktop, release-orchestration-utils, release-vscode-extension, release-website Tag-triggered; interrupting can publish a partial artifact
copilot-setup-steps workflow_dispatch only; a fixed group prevents an accidental double-dispatch

pr-comment-handler.yml

Triggered by issue_comment, which carries no pull_request objectgithub.event.pull_request.number is always null there, so the standard key would collapse every comment across the repo into one group keyed on github.ref. It uses github.event.issue.number instead, and does not cancel: cancelling would silently drop a queued command.

concurrency:
  group: pr-comment-handler-${{ github.event.issue.number }}

Note on formatting

This repo mixes YAML indentation styles. Each block follows the file it lands in — four spaces in monorepo-ci.yml, the five release-*.yml, and validate-version.yml; two spaces elsewhere. Worth knowing when reading the diff, which otherwise looks inconsistent.

Safety checks

  • No merge queue. No merge_group triggers; no branch-protection merge queue; no rulesets configured. Gating cannot stall a queue.
  • No cancel-in-progress on any deploy / release / destroy workflow — asserted mechanically, not by eye.
  • actionlint 1.7.12: 5 findings before, 5 after — none new, none removed. The two pre-existing templates/*.yml syntax findings are in .github/workflows/templates/, untouched here.
  • All 16 files re-parsed post-edit to confirm triggers and jobs survived intact.

Config-only; no job logic, runner, or step changed.

Summary by CodeRabbit

  • Chores
    • Improved automation reliability by preventing overlapping workflow runs for the same pull request, branch, tag, or reference.
    • Automatically cancels superseded pull request checks so newer results complete sooner.
    • Applied concurrency controls across testing, validation, security, deployment, and release workflows.

Superseded runs currently burn to completion instead of cancelling.
Measured in mystira-workspace over the same period: a workflow without
concurrency had 72 supersessions run to completion (856 wasted
wall-minutes, zero cancelled), while one with cancel-in-progress had 38
supersessions cancelled within ~1 min each (36 min total).

CI workflows cancel on pull_request only — ci.yml, lint.yml,
monorepo-ci.yml, security.yml, validate-templates.yml, and
validate-version.yml:

  concurrency:
    group: <workflow-id>-${{ github.event.pull_request.number || github.ref }}
    cancel-in-progress: ${{ github.event_name == 'pull_request' }}

Deploy, release, and destroy workflows get a group with NO
cancel-in-progress so runs serialise instead of being interrupted:
deploy-autopr-engine, deploy-website, destroy-infra, the five release-*
workflows, and copilot-setup-steps.

pr-comment-handler.yml keys on github.event.issue.number, since
issue_comment events carry no pull_request object, and does not cancel —
cancelling it would silently drop a queued command.

Indentation follows each file's existing style (four spaces in
monorepo-ci.yml, release-*.yml, and validate-version.yml; two
elsewhere).

No merge queue is configured on this repo, so gating cannot stall a
queue. actionlint reports the same five pre-existing findings before and
after this change.
@coderabbitai

coderabbitai Bot commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

GitHub Actions workflows now use concurrency groups based on pull requests, issue numbers, or Git references. Pull-request workflows cancel superseded runs. Other workflows prevent overlapping runs for the same reference.

Changes

Workflow concurrency controls

Layer / File(s) Summary
Pull-request-aware workflow concurrency
.github/workflows/ci.yml, .github/workflows/deploy-autopr-engine.yml, .github/workflows/deploy-website.yml, .github/workflows/lint.yml, .github/workflows/monorepo-ci.yml, .github/workflows/pr-comment-handler.yml, .github/workflows/security.yml, .github/workflows/validate-templates.yml, .github/workflows/validate-version.yml
These workflows group runs by pull request or Git reference. Pull-request runs cancel superseded runs. The comment handler groups runs by issue number.
Reference-scoped workflow concurrency
.github/workflows/copilot-setup-steps.yml, .github/workflows/destroy-infra.yml, .github/workflows/release-desktop.yml, .github/workflows/release-orchestration-utils.yml, .github/workflows/release-vscode-extension.yml, .github/workflows/release-website.yml, .github/workflows/release.yml
These workflows group runs by Git reference to prevent overlapping executions for the same reference.

Estimated code review effort: 2 (Simple) | ~10 minutes

Possibly related issues

  • phoenixvc/retort#327 — Covers similar GitHub Actions workflow-level concurrency controls, including changes to ci.yml.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: adding concurrency groups to GitHub Actions workflows that cancel superseded pull request runs.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
🛠️ Fix failing CI checks 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch ci/concurrency-groups

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: bb617f206a

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

types: [created]

concurrency:
group: pr-comment-handler-${{ github.event.issue.number }}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Preserve every queued PR comment event

When three comments arrive on the same PR while the first handler is still running, the second run becomes pending and the third replaces it, so the second comment is never analyzed or applied. Omitting cancel-in-progress does not create an unbounded serialized queue: GitHub concurrency permits at most one running and one pending run and cancels an existing pending run when another is queued. Since each issue_comment run contains a distinct payload, this grouping can silently discard user commands; use a group unique to the comment or another serialization mechanism that retains every event.

Useful? React with 👍 / 👎.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/destroy-infra.yml:
- Around line 6-7: Update the workflow concurrency configuration to use a
ref-independent group such as destroy-infra-production so production teardown
runs serialize across all refs. Configure the concurrency queue to preserve
every pending destroy request by adding queue: max.

In @.github/workflows/monorepo-ci.yml:
- Around line 24-26: Fix the indentation of the child keys in the workflow’s
concurrency block: align group and cancel-in-progress two spaces beneath
concurrency, preserving their existing values.

In @.github/workflows/pr-comment-handler.yml:
- Around line 7-8: Update the concurrency configuration in
.github/workflows/pr-comment-handler.yml at lines 7-8 to add queue: max so
pending commands for the same pull request are retained. Review
.github/workflows/copilot-setup-steps.yml lines 10-11,
.github/workflows/destroy-infra.yml lines 6-7, and
.github/workflows/release-desktop.yml lines 8-9; add queue: max there only if
every invocation for the same ref or tag must execute, otherwise make no change.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 501daecc-a3a1-4ce6-bcfc-5a1f63bbef74

📥 Commits

Reviewing files that changed from the base of the PR and between 7643f0d and bb617f2.

📒 Files selected for processing (16)
  • .github/workflows/ci.yml
  • .github/workflows/copilot-setup-steps.yml
  • .github/workflows/deploy-autopr-engine.yml
  • .github/workflows/deploy-website.yml
  • .github/workflows/destroy-infra.yml
  • .github/workflows/lint.yml
  • .github/workflows/monorepo-ci.yml
  • .github/workflows/pr-comment-handler.yml
  • .github/workflows/release-desktop.yml
  • .github/workflows/release-orchestration-utils.yml
  • .github/workflows/release-vscode-extension.yml
  • .github/workflows/release-website.yml
  • .github/workflows/release.yml
  • .github/workflows/security.yml
  • .github/workflows/validate-templates.yml
  • .github/workflows/validate-version.yml

Comment on lines +6 to +7
concurrency:
group: destroy-infra-${{ github.ref }}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -eu
printf '%s\n' '--- workflow ---'
cat -n .github/workflows/destroy-infra.yml
printf '%s\n' '--- related workflow references ---'
rg -n --glob '.github/workflows/**' 'concurrency:|group:|cancel-in-progress:|environment:|terraform (destroy|apply)|destroy' .github/workflows
printf '%s\n' '--- repository guidance ---'
rg -n 'queue: max|concurrency|destroy-infra|production' .github README.md 2>/dev/null || true

Repository: phoenixvc/codeflow-engine

Length of output: 6353


🌐 Web query:

site:docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions concurrency one running one pending queued job new queued cancels pending

💡 Result:

In the context of GitHub Actions, the behavior you described is the default configuration for concurrency groups [1]. When a concurrency group has a job already running and a new job is triggered, the new job enters a pending state [1]. By default, if another job is already in the pending state within that same group, that existing pending job is canceled and replaced by the new incoming job [1]. This default behavior is defined by the queue property being set to single, which ensures at most one job or workflow run can be pending in the concurrency group at any time [1]. To change this behavior to allow more jobs to queue instead of being canceled, you can set the queue property to max, which allows up to 100 jobs to wait in the queue before additional jobs are canceled [1]. Additionally, you can control whether the currently running job is canceled by setting the cancel-in-progress property to true [1].

Citations:


🏁 Script executed:

#!/bin/bash
set -eu
url='https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions'
python3 - <<'PY'
import urllib.request
url = "https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions"
data = urllib.request.urlopen(url, timeout=20).read().decode("utf-8", "replace")
for needle in ("queue:", "queue", "cancel-in-progress", "pending"):
    print(f"--- {needle} ---")
    start = 0
    found = False
    while True:
        pos = data.lower().find(needle.lower(), start)
        if pos < 0:
            break
        found = True
        print(data[max(0, pos-180):pos+320].replace("\n", " ")[:500])
        start = pos + len(needle)
    if not found:
        print("not found")
PY

Repository: phoenixvc/codeflow-engine

Length of output: 2449


🌐 Web query:

GitHub Actions concurrency queue key queue: max official documentation

💡 Result:

In GitHub Actions, the queue: max property within the concurrency configuration allows up to 100 jobs or workflow runs to be placed in a pending state per concurrency group [1][2]. Key details regarding queue: max: - Functionality: By default, only one run can be in a pending state for a concurrency group, with new runs canceling the previous one [1][3]. Setting queue: max enables a queue of up to 100 pending runs that execute sequentially [1][4]. - Queue Limit: Once the queue reaches the limit of 100 pending runs, any additional jobs or workflow runs added to that concurrency group will be canceled [1][2]. - Restriction: You cannot use queue: max in combination with cancel-in-progress: true [1][3]. Attempting to do so will result in a workflow validation error because they represent conflicting behaviors for handling in-progress runs [1][4]. Example configuration: concurrency: group: production-deploy queue: max [1][3]

Citations:


🏁 Script executed:

#!/bin/bash
set -eu
python3 - <<'PY'
import urllib.request
url = "https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions"
data = urllib.request.urlopen(url, timeout=20).read().decode("utf-8", "replace")
for needle in ("queue:", "queue", "cancel-in-progress", "pending"):
    print(f"--- {needle} ---")
    start = 0
    found = False
    while True:
        pos = data.lower().find(needle.lower(), start)
        if pos < 0:
            break
        found = True
        print(data[max(0, pos-180):pos+320].replace("\n", " ")[:500])
        start = pos + len(needle)
    if not found:
        print("not found")
PY

Repository: phoenixvc/codeflow-engine

Length of output: 2449


Serialize all production destroys and preserve pending requests.

  • Use a ref-independent group such as destroy-infra-production; github.ref allows destroys from different refs to run concurrently.
  • If every teardown request must execute, add queue: max; otherwise, a new pending run replaces the existing pending run in the same group.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/destroy-infra.yml around lines 6 - 7, Update the workflow
concurrency configuration to use a ref-independent group such as
destroy-infra-production so production teardown runs serialize across all refs.
Configure the concurrency queue to preserve every pending destroy request by
adding queue: max.

Comment on lines +24 to +26
concurrency:
group: monorepo-ci-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Fix the concurrency block indentation.

The YAML validator flags Line 25 because the child keys use four spaces. Reindent the block to the expected two-space level.

Proposed fix
 concurrency:
-    group: monorepo-ci-${{ github.event.pull_request.number || github.ref }}
-    cancel-in-progress: ${{ github.event_name == 'pull_request' }}
+  group: monorepo-ci-${{ github.event.pull_request.number || github.ref }}
+  cancel-in-progress: ${{ github.event_name == 'pull_request' }}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
concurrency:
group: monorepo-ci-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
concurrency:
group: monorepo-ci-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
🧰 Tools
🪛 GitHub Check: Validate YAML Files

[failure] 25-25:
25:5 [indentation] wrong indentation: expected 2 but found 4

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/monorepo-ci.yml around lines 24 - 26, Fix the indentation
of the child keys in the workflow’s concurrency block: align group and
cancel-in-progress two spaces beneath concurrency, preserving their existing
values.

Source: Linters/SAST tools

Comment on lines +7 to +8
concurrency:
group: pr-comment-handler-${{ github.event.issue.number }}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -eu
for f in \
  .github/workflows/pr-comment-handler.yml \
  .github/workflows/copilot-setup-steps.yml \
  .github/workflows/destroy-infra.yml \
  .github/workflows/release-desktop.yml
do
  printf '\n===== %s =====\n' "$f"
  sed -n '1,45p' "$f"
done
printf '\n===== concurrency and queue keys =====\n'
rg -n -C 3 'concurrency:|cancel-in-progress|queue:' .github/workflows

Repository: phoenixvc/codeflow-engine

Length of output: 12590


🌐 Web query:

GitHub Actions workflow syntax concurrency pending runs queue max replace pending run cancel-in-progress official documentation

💡 Result:

In GitHub Actions, the concurrency keyword controls how multiple runs of a workflow or job within the same concurrency group behave [1][2]. Default Behavior By default, a concurrency group allows at most one running job or workflow and one pending job or workflow [3][2]. If a new job or workflow is queued while another is already pending in the same group, the existing pending run is automatically canceled and replaced by the new one [3][2]. Queueing with queue: max To support sequential processing of multiple runs, you can use the queue property [1][4]. Setting queue: max allows up to 100 jobs or workflow runs to be queued in a single concurrency group [5][6]. Once this limit is reached, any additional runs entering the group will be rejected [6]. This setting is intended for workflows that must execute in order, such as deployments to a shared environment [4]. Cancellation Control with cancel-in-progress The cancel-in-progress property determines whether an active, running job is terminated when a new job enters the concurrency group [3][2]. - When cancel-in-progress: true, any currently running job or workflow in the group will be canceled when a new one starts [3][2]. - When cancel-in-progress: false (the default) or omitted, active jobs are allowed to finish, and the new job will wait in the queue [2][7]. Combining these settings allows you to customize whether you prefer to stop old runs to save resources or queue them to ensure all tasks execute sequentially [8][4]. Concurrency group names must be unique if you intend to isolate workflows; if multiple workflows share the same group name, they will compete for that single concurrency slot [5].

Citations:


🏁 Script executed:

#!/bin/bash
set -eu
python3 - <<'PY'
from pathlib import Path
import re

files = [
    ".github/workflows/pr-comment-handler.yml",
    ".github/workflows/copilot-setup-steps.yml",
    ".github/workflows/destroy-infra.yml",
    ".github/workflows/release-desktop.yml",
]
for name in files:
    text = Path(name).read_text(encoding="utf-8-sig")
    concurrency = text.split("concurrency:", 1)[1].split("\njobs:", 1)[0]
    group = re.search(r"group:\s*(.+)", concurrency)
    cancel = re.search(r"cancel-in-progress:\s*(.+)", concurrency)
    queue = re.search(r"queue:\s*(.+)", concurrency)
    print(name)
    print("  group:", group.group(1).strip() if group else "<missing>")
    print("  cancel-in-progress:", cancel.group(1).strip() if cancel else "<omitted>")
    print("  queue:", queue.group(1).strip() if queue else "<omitted>")
    print("  trigger:", text.split("on:", 1)[1].split("concurrency:", 1)[0].strip().replace("\n", " "))
PY

printf '\n===== official documentation references =====\n'
curl -L --fail --silent --show-error \
  https://docs.github.com/en/actions/reference/workflows-and-actions/workflow-syntax \
  | rg -n -i -C 3 'queue: max|pending|concurrency group|cancel-in-progress' | head -120

Repository: phoenixvc/codeflow-engine

Length of output: 20500


🌐 Web query:

site:docs.github.com/en/actions "queue: max" concurrency GitHub Actions

💡 Result:

In GitHub Actions, the queue: max setting is used within the concurrency configuration to allow multiple jobs or workflow runs to wait in a pending state within the same concurrency group [1][2]. Key details regarding queue: max: - Capacity: When queue: max is specified, up to 100 jobs or workflow runs can be in a pending (queued) state simultaneously for that specific concurrency group [3][1]. - Queue Full Behavior: If the concurrency group's queue reaches this limit of 100 pending runs, any additional jobs or workflow runs triggered for that group will be canceled [1][2]. - Comparison to Default: By default, GitHub Actions uses a single queue setting, which permits only one pending job or workflow run at a time; when a new run is triggered, it cancels any existing pending run in that group [1][2]. - Purpose: This setting is useful when you need multiple runs to execute sequentially without them automatically canceling each other, ensuring they all eventually run in the order they were triggered [4]. Note that this queue limit is specific to the concurrency feature and is distinct from other limitations, such as matrix strategy parallelism (max-parallel) or total runner concurrency limits, which are managed at the account or organization level [3][5].

Citations:


Preserve required pending runs with queue: max. GitHub Actions cancels an existing pending run when a new run enters the same group. Add queue: max when every invocation must execute.

  • pr-comment-handler.yml: required to avoid dropping commands for the same pull request.
  • copilot-setup-steps.yml: add it if every setup invocation for the same ref must execute.
  • destroy-infra.yml: add it if every teardown request for the same ref must execute.
  • release-desktop.yml: add it only if repeated runs for the same tag ref must execute. Different tag refs use different groups.

queue: max retains up to 100 pending runs. Additional runs are canceled.

📍 Affects 4 files
  • .github/workflows/pr-comment-handler.yml#L7-L8 (this comment)
  • .github/workflows/copilot-setup-steps.yml#L10-L11
  • .github/workflows/destroy-infra.yml#L6-L7
  • .github/workflows/release-desktop.yml#L8-L9
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/pr-comment-handler.yml around lines 7 - 8, Update the
concurrency configuration in .github/workflows/pr-comment-handler.yml at lines
7-8 to add queue: max so pending commands for the same pull request are
retained. Review .github/workflows/copilot-setup-steps.yml lines 10-11,
.github/workflows/destroy-infra.yml lines 6-7, and
.github/workflows/release-desktop.yml lines 8-9; add queue: max there only if
every invocation for the same ref or tag must execute, otherwise make no change.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant