diff --git a/.github/workflows/label-pr-review-state.yml b/.github/workflows/label-pr-review-state.yml index 1bffbd3e02..7d9baca0a6 100644 --- a/.github/workflows/label-pr-review-state.yml +++ b/.github/workflows/label-pr-review-state.yml @@ -44,6 +44,8 @@ permissions: issues: write checks: read statuses: write + # Reading a commit's parents is metadata only; no PR code is checked out or executed. + contents: read # Concurrency is keyed at two levels (issue #1707): # @@ -91,6 +93,7 @@ jobs: runs-on: ubuntu-latest outputs: pr_numbers: ${{ steps.resolve.outputs.pr_numbers }} + open_pr_heads: ${{ steps.resolve.outputs.open_pr_heads }} env: # Mirrors the workflow-level concurrency group so the run's queueing # identity is visible in the logs (kept in sync by the workflow tests). @@ -131,6 +134,7 @@ jobs: // associated PRs, with a fork-safe backfill by exact head owner, // branch, and SHA when GitHub left pull_requests empty. let prNumbers = []; + let openPrsCache = null; if (context.payload.pull_request?.number) { prNumbers = [context.payload.pull_request.number]; } else if (context.eventName === 'issue_comment') { @@ -163,14 +167,14 @@ jobs: ); } } else { - const openPrs = await github.paginate(github.rest.pulls.list, { + openPrsCache = await github.paginate(github.rest.pulls.list, { owner, repo, state: 'open', per_page: 100, }); // GitHub rejects matrices over 256 jobs, so the sweep pages the // sorted open-PR list: consecutive runs rotate through // fixed-size windows by run number, covering every open PR on a // bounded cadence without ever exceeding the limit. - const allNumbers = openPrs.map(pr => pr.number).sort((a, b) => a - b); + const allNumbers = openPrsCache.map(pr => pr.number).sort((a, b) => a - b); const sweepPageSize = 200; const pageCount = Math.max(1, Math.ceil(allNumbers.length / sweepPageSize)); const page = context.runNumber % pageCount; @@ -189,6 +193,32 @@ jobs: ); core.setOutput('pr_numbers', JSON.stringify(prNumbers)); + // Build the open-PR head map once here for every shard: a sweep would otherwise + // multiply the paginated open-PR read by every shard against the Actions token + // budget. Sweep triggers already hold the full open list, so the map costs them + // no extra read. An empty output means the map was unavailable (or nothing was + // resolved); shards then skip the stacked label rather than deciding from an + // empty map. + let openPrHeadsOutput = ''; + if (prNumbers.length > 0) { + try { + const openPrsForMap = openPrsCache ?? (await github.paginate(github.rest.pulls.list, { + owner, repo, state: 'open', per_page: 100, + })); + const heads = {}; + for (const candidate of openPrsForMap) { + if (candidate.head?.sha) heads[candidate.head.sha.toLowerCase()] = candidate.number; + } + // An open-PR list with no entries is anomalous (the reconciliation + // target itself is open), so treat it like an unavailable map: + // shards must not decide from a map that looks empty. + if (Object.keys(heads).length > 0) openPrHeadsOutput = JSON.stringify(heads); + } catch (error) { + core.warning(`Could not read the open pull request map: ${error.message}; shards will skip the stacked label this run`); + } + } + core.setOutput('open_pr_heads', openPrHeadsOutput); + reconcile: name: Zoo Code / reconcile PR review state needs: resolve @@ -230,6 +260,7 @@ jobs: runs-on: ubuntu-latest env: PR_NUMBER: ${{ matrix.pr_number }} + OPEN_PR_HEADS: ${{ needs.resolve.outputs.open_pr_heads }} # Mirrors this job's concurrency group so a superseded shard's queueing # identity is visible in the logs (kept in sync by the workflow tests). CONCURRENCY_GROUP: >- @@ -286,6 +317,11 @@ jobs: color: 'c2e0c6', description: 'Fresh community approval on the current head; maintainer review still required', }, + { + name: 'stacked', + color: 'c2c2c2', + description: 'Head commit sits on another open PR, so its diff is only this unit', + }, ]; const guideMarker = ''; const codeRabbitLabelMarkerPrefix = '