diff --git a/.github/workflows/qwen-code-pr-review.yml b/.github/workflows/qwen-code-pr-review.yml index 0370d0024a8..cac7261e618 100644 --- a/.github/workflows/qwen-code-pr-review.yml +++ b/.github/workflows/qwen-code-pr-review.yml @@ -159,9 +159,17 @@ jobs: fi review-config: + # Bot-requested review_requested only: a CODEOWNERS-covered PR open + # auto-requests every owner individually (#8945), spawning one + # review_requested run per owner. Only the run where the bot itself is + # the requested reviewer can reach review-pr, so the human-requested + # siblings must skip here instead of each spending a runner. KEEP IN + # SYNC with the review_requested clauses in precheck-pr.if and + # authorize.if, and with the bot_login constant below. if: |- github.event_name == 'pull_request_target' && - github.event.action == 'review_requested' + github.event.action == 'review_requested' && + github.event.requested_reviewer.login == 'qwen-code-ci-bot' runs-on: '${{ (github.repository == ''QwenLM/qwen-code'' && vars.MAINTAINER_ECS_RUNNER_DISABLED != ''true'') && fromJSON(''["self-hosted", "linux", "x64", "ecs-qwen"]'') || fromJSON(''["ubuntu-latest"]'') }}' permissions: {} outputs: @@ -225,6 +233,11 @@ jobs: # Only run for PR-target events and supported command comments — not every # unrelated comment — to avoid spawning a job per comment. The downstream # `if`s still do the exact command body match; this prefix is just a filter. + # review_requested must additionally request the bot itself: a + # CODEOWNERS-covered PR open auto-requests every owner individually + # (#8945), and precheck-pr's identical predicate only covers fork PRs — + # without this clause each same-repo sibling run spends an authorize job + # (permission API + runner slot) before review-pr no-op exits. if: |- !cancelled() && (github.event_name != 'pull_request_target' || @@ -232,6 +245,9 @@ jobs: (github.event_name != 'pull_request_target' || github.event.pull_request.head.repo.full_name == github.repository || needs.precheck-pr.outputs.decision == 'allow_triage') && + (github.event_name != 'pull_request_target' || + github.event.action != 'review_requested' || + github.event.requested_reviewer.login == 'qwen-code-ci-bot') && (github.event_name == 'pull_request_target' || (github.event_name == 'workflow_dispatch' && github.event.inputs.command == 'resolve') || diff --git a/scripts/tests/qwen-pr-review-workflow.test.js b/scripts/tests/qwen-pr-review-workflow.test.js index db4e46297dc..5af2cfb0c69 100644 --- a/scripts/tests/qwen-pr-review-workflow.test.js +++ b/scripts/tests/qwen-pr-review-workflow.test.js @@ -2517,3 +2517,47 @@ describe('bot comment markers', () => { expect(ackLine).toContain('"$RUN_URL"'); }); }); + +describe('review_requested burst coalescing (#8945)', () => { + // Opening a same-repo PR that touches CODEOWNERS-covered paths auto-requests + // every owner individually, so one PR open emits one `review_requested` run + // per owner (observed: five within the same second on #8830/#9142). Only + // the bot-requested run can reach `review-pr`; the human-requested siblings + // used to spend an `authorize` job (permission API + runner slot) each + // before no-op exiting. `authorize` and `review-config` must filter those + // siblings at the job level so they complete as instant all-skipped runs. + const doc = parse(workflow); + + const botLoginMatch = doc.jobs['review-config'].steps[0].run.match( + /bot_login=([A-Za-z0-9-]+)/, + ); + const botLogin = botLoginMatch ? botLoginMatch[1] : ''; + + const botRequestedClause = new RegExp( + [ + String.raw`\(\s*github\.event_name != 'pull_request_target' \|\|`, + String.raw`\s*github\.event\.action != 'review_requested' \|\|`, + String.raw`\s*github\.event\.requested_reviewer\.login == '${botLogin}'\s*\)`, + ].join(''), + ); + + it('resolves the bot login from review-config, not a paraphrase', () => { + expect(botLogin).toBe('qwen-code-ci-bot'); + }); + + it('filters non-bot review_requested events before authorize spends compute', () => { + // The same disjunction precheck-pr already applies to fork PRs; mirroring + // it here covers same-repo PRs, which precheck-pr deliberately skips. + expect(doc.jobs['authorize'].if).toMatch(botRequestedClause); + }); + + it('keeps review-config from running for non-bot review_requested events', () => { + // review-config exists to feed bot_login into review-pr; only the + // bot-requested run can get there, so every other sibling is pure waste. + const cond = doc.jobs['review-config'].if; + expect(cond).toContain("github.event.action == 'review_requested'"); + expect(cond).toContain( + `github.event.requested_reviewer.login == '${botLogin}'`, + ); + }); +});