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: