From ba7561af4915c11c94268dfdecf19a26970c0725 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=98=93=E8=89=AF?= <1204183885@qq.com> Date: Sat, 4 Jul 2026 22:16:47 +0800 Subject: [PATCH] fix(ci): Stop review bots for closed PRs (#6304) * fix(ci): stop review bots for closed PRs * test(ci): scope closed review workflow assertions * test(ci): cover closed PR review guards --- .github/workflows/qwen-code-pr-review.yml | 14 ++++++++++++-- .github/workflows/qwen-triage.yml | 8 +++++++- scripts/tests/qwen-resolve-workflow.test.js | 20 ++++++++++++++++++++ scripts/tests/qwen-triage-workflow.test.js | 10 ++++++++++ 4 files changed, 49 insertions(+), 3 deletions(-) diff --git a/.github/workflows/qwen-code-pr-review.yml b/.github/workflows/qwen-code-pr-review.yml index d86ed4a7ac..34eea28157 100644 --- a/.github/workflows/qwen-code-pr-review.yml +++ b/.github/workflows/qwen-code-pr-review.yml @@ -8,6 +8,7 @@ on: - 'reopened' - 'ready_for_review' - 'review_requested' + - 'closed' issue_comment: types: ['created'] pull_request_review_comment: @@ -48,18 +49,25 @@ on: type: 'boolean' concurrency: - # PR lifecycle events share a PR-scoped group so new pushes restart the delay. + # PR lifecycle events share a PR-scoped group so new pushes restart the delay + # and closed PRs stop any in-flight lifecycle review. # Comment/review events use per-run groups to avoid cancelling active reviews. group: >- ${{ github.event_name == 'pull_request_target' && format('qwen-pr-review-pr-{0}', github.event.pull_request.number) || format('qwen-pr-review-run-{0}', github.run_id) }} - cancel-in-progress: "${{ github.event_name == 'pull_request_target' && github.event.action == 'synchronize' }}" + cancel-in-progress: >- + ${{ + github.event_name == 'pull_request_target' && + (github.event.action == 'synchronize' || + github.event.action == 'closed') + }} jobs: precheck-pr: if: |- github.event_name == 'pull_request_target' && + github.event.action != 'closed' && github.event.pull_request.head.repo.full_name != github.repository && (github.event.action != 'review_requested' || github.event.requested_reviewer.login == 'qwen-code-ci-bot') @@ -207,6 +215,8 @@ jobs: # `if`s still do the exact command body match; this prefix is just a filter. if: |- always() && + (github.event_name != 'pull_request_target' || + github.event.action != 'closed') && (github.event_name != 'pull_request_target' || github.event.pull_request.head.repo.full_name == github.repository || needs.precheck-pr.outputs.decision == 'allow_triage') && diff --git a/.github/workflows/qwen-triage.yml b/.github/workflows/qwen-triage.yml index 9aaa7d4c61..a3c59ce7ec 100644 --- a/.github/workflows/qwen-triage.yml +++ b/.github/workflows/qwen-triage.yml @@ -62,6 +62,7 @@ jobs: needs.precheck-pr.outputs.decision == 'allow_triage') && (github.event_name == 'pull_request_target' || (github.event_name == 'issue_comment' && + github.event.issue.state == 'open' && (startsWith(github.event.comment.body, '@qwen-code /triage') || github.event.comment.body == '@qwen-code /tmux' || startsWith(github.event.comment.body, '@qwen-code /tmux '))) || @@ -160,7 +161,8 @@ jobs: (github.event.pull_request.draft == true || needs.authorize.outputs.should_run != 'true')) || (github.event_name == 'issue_comment' && - needs.authorize.outputs.should_run != 'true') + (github.event.issue.state != 'open' || + needs.authorize.outputs.should_run != 'true')) ) && format('{0}-run-{1}', github.workflow, github.run_id) || format('{0}-{1}', github.workflow, github.event.issue.number || github.event.pull_request.number || github.event.inputs.number) @@ -172,6 +174,7 @@ jobs: (((github.event_name == 'pull_request_target' && github.event.pull_request.draft == false) || (github.event_name == 'issue_comment' && + github.event.issue.state == 'open' && startsWith(github.event.comment.body, '@qwen-code /triage'))) && needs.authorize.outputs.should_run == 'true') }} @@ -197,6 +200,7 @@ jobs: ((github.event_name == 'pull_request_target' && github.event.pull_request.draft == false) || (github.event_name == 'issue_comment' && + github.event.issue.state == 'open' && startsWith(github.event.comment.body, '@qwen-code /triage'))) && needs.authorize.outputs.should_run == 'true' ) @@ -298,6 +302,7 @@ jobs: ( (github.event_name == 'issue_comment' && github.event.issue.pull_request && + github.event.issue.state == 'open' && (github.event.comment.body == '@qwen-code /tmux' || startsWith(github.event.comment.body, '@qwen-code /tmux ')) && needs.authorize.outputs.should_run == 'true') || @@ -316,6 +321,7 @@ jobs: ( ((github.event_name == 'issue_comment' && github.event.issue.pull_request && + github.event.issue.state == 'open' && (github.event.comment.body == '@qwen-code /tmux' || startsWith(github.event.comment.body, '@qwen-code /tmux '))) || (github.event_name == 'workflow_dispatch' && diff --git a/scripts/tests/qwen-resolve-workflow.test.js b/scripts/tests/qwen-resolve-workflow.test.js index 169b7f46e5..abd17fdd64 100644 --- a/scripts/tests/qwen-resolve-workflow.test.js +++ b/scripts/tests/qwen-resolve-workflow.test.js @@ -65,6 +65,20 @@ describe('qwen resolve workflow', () => { ); }); + it('cancels in-flight lifecycle reviews when the PR closes', () => { + const concurrencyStart = workflow.indexOf('\nconcurrency:'); + const concurrency = workflow.slice( + concurrencyStart, + workflow.indexOf('\njobs:', concurrencyStart), + ); + + expect(workflow).toContain("- 'closed'"); + expect(concurrency).toContain("github.event.action == 'closed'"); + expect(concurrency).toContain( + "format('qwen-pr-review-pr-{0}', github.event.pull_request.number)", + ); + }); + it('listens for /resolve comments', () => { expect(workflow).toContain( "github.event.comment.body == '@qwen-code /resolve'", @@ -178,6 +192,12 @@ describe('qwen resolve workflow', () => { : workflow.slice(resolveJobStart, resolveJobStart + 1 + nextJob); const reviewJob = job(workflow, 'review-pr'); const authorizeJob = job(workflow, 'authorize'); + const precheckJob = job(workflow, 'precheck-pr'); + + it('keeps closed PR events from running precheck or authorize jobs', () => { + expect(precheckJob).toContain("github.event.action != 'closed'"); + expect(authorizeJob).toContain("github.event.action != 'closed'"); + }); it('does not require fork PR authors to have write permission for automatic review', () => { const authorizeStep = step( diff --git a/scripts/tests/qwen-triage-workflow.test.js b/scripts/tests/qwen-triage-workflow.test.js index a110614e69..30b3be98e2 100644 --- a/scripts/tests/qwen-triage-workflow.test.js +++ b/scripts/tests/qwen-triage-workflow.test.js @@ -60,6 +60,16 @@ describe('qwen-triage tmux workflow', () => { ); }); + it('requires open issues or PRs for comment-triggered triage', () => { + const authorizeJob = job('authorize'); + const triageJob = job('triage'); + const tmuxJob = job('tmux-testing'); + + expect(authorizeJob).toContain("github.event.issue.state == 'open'"); + expect(triageJob).toContain("github.event.issue.state == 'open'"); + expect(tmuxJob).toContain("github.event.issue.state == 'open'"); + }); + it('escapes embedded tmux artifacts without bash pattern replacement ampersands', () => { const postStep = step('Post tmux result comment');