From 5c5077ab51a788706bf5445bf39bac195d5b682b Mon Sep 17 00:00:00 2001 From: Nathan Heskew Date: Wed, 26 Aug 2026 14:15:16 -0700 Subject: [PATCH] ci: ready_for_review runs review but never cancels MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit kriszyp follow-up finding on the harper-pro twin (harper-pro#767): the label-opted ready_for_review event sat in the shared eligible concurrency group, so on a bot-authored PR it could cancel the trusted labeler's in-flight review and then be rejected by author-based authorization — no completed review. The cancelling set (concurrency predicate) now excludes ready_for_review; the running set (job gate) keeps it. Revisit if the reusable gains persisted-label authorization. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01S94XethbGXpAb4DRKMD4kt --- .github/workflows/claude-review.yml | 9 ++++++--- .github/workflows/gemini-review.yml | 9 ++++++--- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/.github/workflows/claude-review.yml b/.github/workflows/claude-review.yml index 3b91edabab..cc90d00c78 100644 --- a/.github/workflows/claude-review.yml +++ b/.github/workflows/claude-review.yml @@ -21,12 +21,15 @@ on: types: [opened, synchronize, reopened, ready_for_review, labeled] concurrency: - # NOTE: this predicate and the review job's `if:` must stay textually - # in sync — GitHub Actions YAML has no anchors to share them. + # NOTE: this predicate is deliberately NARROWER than the review job's + # `if:` — the cancelling set must be a subset of the running set. + # ready_for_review runs review (job gate) but never cancel: their + # authorization is author-based, so on a bot-authored PR they could + # kill a labeler-authorized run and then be rejected themselves. # Ineligible events (per the review job's opt-in gate) get a unique # run_id group so they can never cancel an in-flight eligible review — # workflow-level cancel-in-progress fires before job `if:` is evaluated. - group: claude-review-${{ github.event.pull_request.number }}-${{ ((github.event.action == 'labeled' && github.event.label.name == 'claude-review') || (github.event.action != 'labeled' && vars.CLAUDE_ALWAYS_ON == 'true') || (github.event.action == 'ready_for_review' && contains(github.event.pull_request.labels.*.name, 'claude-review'))) && 'eligible' || github.run_id }} + group: claude-review-${{ github.event.pull_request.number }}-${{ ((github.event.action == 'labeled' && github.event.label.name == 'claude-review') || (github.event.action != 'labeled' && vars.CLAUDE_ALWAYS_ON == 'true')) && 'eligible' || github.run_id }} cancel-in-progress: true jobs: diff --git a/.github/workflows/gemini-review.yml b/.github/workflows/gemini-review.yml index c8941064f6..bcab436224 100644 --- a/.github/workflows/gemini-review.yml +++ b/.github/workflows/gemini-review.yml @@ -39,10 +39,13 @@ concurrency: # in parallel on the same PR. cancel-in-progress is per-group, so a # synchronize push cancels the in-flight Gemini run without touching # the Claude run (and vice versa). - # NOTE: this predicate and the review job's `if:` must stay textually - # in sync — GitHub Actions YAML has no anchors to share them. + # NOTE: this predicate is deliberately NARROWER than the review job's + # `if:` — the cancelling set must be a subset of the running set. + # ready_for_review runs review (job gate) but never cancel: their + # authorization is author-based, so on a bot-authored PR they could + # kill a labeler-authorized run and then be rejected themselves. # Ineligible events get a unique run_id group — see claude-review.yml. - group: gemini-review-${{ github.event.pull_request.number }}-${{ ((github.event.action == 'labeled' && github.event.label.name == 'gemini-review') || (github.event.action != 'labeled' && vars.GEMINI_ALWAYS_ON == 'true') || (github.event.action == 'ready_for_review' && contains(github.event.pull_request.labels.*.name, 'gemini-review'))) && 'eligible' || github.run_id }} + group: gemini-review-${{ github.event.pull_request.number }}-${{ ((github.event.action == 'labeled' && github.event.label.name == 'gemini-review') || (github.event.action != 'labeled' && vars.GEMINI_ALWAYS_ON == 'true')) && 'eligible' || github.run_id }} cancel-in-progress: true jobs: