-
Notifications
You must be signed in to change notification settings - Fork 1
ci: introduce deterministic native merge queue gate + PR queue visibility #22294
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,59 +1,137 @@ | ||||||||||||||||||||||
| name: Merge Queue CI | ||||||||||||||||||||||
| name: merge-queue | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| on: | ||||||||||||||||||||||
| merge_group: | ||||||||||||||||||||||
| types: [checks_requested] | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| concurrency: | ||||||||||||||||||||||
| group: ${{ github.workflow }}-${{ github.ref }} | ||||||||||||||||||||||
| cancel-in-progress: true | ||||||||||||||||||||||
| pull_request: | ||||||||||||||||||||||
| types: [enqueued, dequeued, synchronize, reopened, ready_for_review] | ||||||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
The Useful? React with 👍 / 👎. |
||||||||||||||||||||||
| branches: [main] | ||||||||||||||||||||||
|
Comment on lines
+6
to
+8
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🌐 Web query:
💡 Result: The valid activity types (specified using the types key) for the GitHub Actions pull_request event trigger are: - assigned - unassigned - review_requested - review_request_removed - labeled - unlabeled - opened - edited - closed - reopened - synchronize - ready_for_review - locked - unlocked - auto_merge_disabled - auto_merge_enabled By default, workflows trigger on opened, synchronize, and reopened if no types are specified. These activity types correspond to the action values in the pull_request webhook payload, which GitHub Actions uses to trigger workflows. The full list is derived from official GitHub documentation on webhook events and confirmed by examples in events-that-trigger-workflows and blog posts referencing the docs. For the most up-to-date list as of 2026, refer to https://docs.github.com/en/webhooks/webhook-events-and-payloads#pull_request and https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#pull_request. Citations:
🌐 Web query:
💡 Result: The valid activity type for the GitHub Actions merge_group event trigger is checks_requested. Unlike many other events, the merge_group event does not support multiple activity types that can be filtered with the 'types' keyword. It triggers specifically when checks are requested for a merge group in a merge queue, with GITHUB_SHA set to the merge group SHA and GITHUB_REF to the merge group ref. Citations:
The This means lines 7-8 contain invalid syntax that will cause the workflow to fail. Remove the invalid event types from the pull_request:
- types: [enqueued, dequeued, synchronize, reopened, ready_for_review]
+ types: [synchronize, reopened, ready_for_review]
branches: [main]Note: The 🤖 Prompt for AI Agents |
||||||||||||||||||||||
|
|
||||||||||||||||||||||
| permissions: | ||||||||||||||||||||||
| actions: read | ||||||||||||||||||||||
| checks: read | ||||||||||||||||||||||
| contents: read | ||||||||||||||||||||||
| pull-requests: write | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| concurrency: | ||||||||||||||||||||||
| group: merge-queue-main | ||||||||||||||||||||||
| cancel-in-progress: false | ||||||||||||||||||||||
|
Comment on lines
14
to
+16
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Workflow-level concurrency is now a single fixed group for both Useful? React with 👍 / 👎. |
||||||||||||||||||||||
|
|
||||||||||||||||||||||
| jobs: | ||||||||||||||||||||||
| merge-heavy: | ||||||||||||||||||||||
| name: Heavy Checks | ||||||||||||||||||||||
| queue-visibility: | ||||||||||||||||||||||
| if: github.event_name == 'pull_request' | ||||||||||||||||||||||
| runs-on: ubuntu-latest | ||||||||||||||||||||||
| steps: | ||||||||||||||||||||||
| - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 | ||||||||||||||||||||||
| - name: Publish queue state comment | ||||||||||||||||||||||
| uses: actions/github-script@v7 | ||||||||||||||||||||||
| with: | ||||||||||||||||||||||
| github-token: ${{ secrets.GITHUB_TOKEN }} | ||||||||||||||||||||||
| script: | | ||||||||||||||||||||||
| const pr = context.payload.pull_request; | ||||||||||||||||||||||
| if (!pr) { | ||||||||||||||||||||||
| core.info('No pull_request payload, skipping.'); | ||||||||||||||||||||||
| return; | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| const marker = '<!-- merge-queue-status -->'; | ||||||||||||||||||||||
| const state = context.payload.action; | ||||||||||||||||||||||
| const branch = 'main'; | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| let queueSize = 0; | ||||||||||||||||||||||
| let positionText = 'Not currently in queue'; | ||||||||||||||||||||||
| let reason = 'No dequeue reason reported.'; | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| try { | ||||||||||||||||||||||
| const queue = await github.request('GET /repos/{owner}/{repo}/merge-queue', { | ||||||||||||||||||||||
| owner: context.repo.owner, | ||||||||||||||||||||||
| repo: context.repo.repo, | ||||||||||||||||||||||
| branch, | ||||||||||||||||||||||
| }); | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| const entries = queue.data?.entries ?? []; | ||||||||||||||||||||||
| queueSize = entries.length; | ||||||||||||||||||||||
| const index = entries.findIndex((entry) => entry.number === pr.number); | ||||||||||||||||||||||
| if (index >= 0) { | ||||||||||||||||||||||
| positionText = `${index + 1} of ${queueSize}`; | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| } catch (error) { | ||||||||||||||||||||||
| core.warning(`Unable to read merge queue: ${error.message}`); | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| if (state === 'dequeued') { | ||||||||||||||||||||||
| reason = context.payload.reason || 'Validation failed or PR no longer mergeable.'; | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| const body = [ | ||||||||||||||||||||||
| marker, | ||||||||||||||||||||||
| `### Merge Queue Status`, | ||||||||||||||||||||||
| `- **PR:** #${pr.number}`, | ||||||||||||||||||||||
| `- **Event:** ${state}`, | ||||||||||||||||||||||
| `- **Queue position:** ${positionText}`, | ||||||||||||||||||||||
| `- **Queue depth:** ${queueSize}`, | ||||||||||||||||||||||
| `- **Removal reason:** ${reason}`, | ||||||||||||||||||||||
| '', | ||||||||||||||||||||||
| '_Queue is FIFO and each candidate is validated on latest `main` through `merge_group` before merge._', | ||||||||||||||||||||||
| ].join('\n'); | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| const comments = await github.paginate(github.rest.issues.listComments, { | ||||||||||||||||||||||
| owner: context.repo.owner, | ||||||||||||||||||||||
| repo: context.repo.repo, | ||||||||||||||||||||||
| issue_number: pr.number, | ||||||||||||||||||||||
| per_page: 100, | ||||||||||||||||||||||
| }); | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| const existing = comments.find((comment) => comment.body?.includes(marker)); | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| if (existing) { | ||||||||||||||||||||||
| await github.rest.issues.updateComment({ | ||||||||||||||||||||||
| owner: context.repo.owner, | ||||||||||||||||||||||
| repo: context.repo.repo, | ||||||||||||||||||||||
| comment_id: existing.id, | ||||||||||||||||||||||
| body, | ||||||||||||||||||||||
| }); | ||||||||||||||||||||||
| } else { | ||||||||||||||||||||||
| await github.rest.issues.createComment({ | ||||||||||||||||||||||
| owner: context.repo.owner, | ||||||||||||||||||||||
| repo: context.repo.repo, | ||||||||||||||||||||||
| issue_number: pr.number, | ||||||||||||||||||||||
| body, | ||||||||||||||||||||||
| }); | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| merge-queue-critical: | ||||||||||||||||||||||
| if: github.event_name == 'merge_group' | ||||||||||||||||||||||
| runs-on: ubuntu-latest | ||||||||||||||||||||||
| timeout-minutes: 30 | ||||||||||||||||||||||
| steps: | ||||||||||||||||||||||
| - name: Checkout merge candidate | ||||||||||||||||||||||
| uses: actions/checkout@v4 | ||||||||||||||||||||||
| with: | ||||||||||||||||||||||
| fetch-depth: 0 | ||||||||||||||||||||||
| fetch-tags: true | ||||||||||||||||||||||
| - uses: pnpm/action-setup@f40ffcd9367d9f12939873eb1018b921a783ffaa | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| - name: Setup pnpm | ||||||||||||||||||||||
| uses: pnpm/action-setup@v4 | ||||||||||||||||||||||
| with: | ||||||||||||||||||||||
| version: 9.15.4 | ||||||||||||||||||||||
| - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 | ||||||||||||||||||||||
| with: | ||||||||||||||||||||||
| node-version: 24 | ||||||||||||||||||||||
| cache: 'pnpm' | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| - name: Check for PR Validation Artifact | ||||||||||||||||||||||
| id: validate | ||||||||||||||||||||||
| uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 | ||||||||||||||||||||||
| - name: Setup Node | ||||||||||||||||||||||
| uses: actions/setup-node@v4 | ||||||||||||||||||||||
| with: | ||||||||||||||||||||||
| name: ci-validation-${{ github.event.merge_group.head_sha }} | ||||||||||||||||||||||
| path: . | ||||||||||||||||||||||
| continue-on-error: true | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| - name: Install | ||||||||||||||||||||||
| if: steps.validate.outcome != 'success' | ||||||||||||||||||||||
| run: pnpm install | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| - name: Build | ||||||||||||||||||||||
| if: steps.validate.outcome != 'success' | ||||||||||||||||||||||
| run: pnpm -r build || echo "Build skipped or no scripts" | ||||||||||||||||||||||
| node-version: 24 | ||||||||||||||||||||||
| cache: pnpm | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| - name: Test | ||||||||||||||||||||||
| if: steps.validate.outcome != 'success' | ||||||||||||||||||||||
| run: pnpm -r test || echo "Test skipped or no scripts" | ||||||||||||||||||||||
| - name: Install dependencies | ||||||||||||||||||||||
| run: pnpm install --frozen-lockfile | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| - name: Audit | ||||||||||||||||||||||
| if: steps.validate.outcome != 'success' | ||||||||||||||||||||||
| run: pnpm audit || true | ||||||||||||||||||||||
| - name: Critical validation suite | ||||||||||||||||||||||
| run: | | ||||||||||||||||||||||
| pnpm run lint --if-present | ||||||||||||||||||||||
| pnpm run typecheck --if-present | ||||||||||||||||||||||
| pnpm run test --if-present | ||||||||||||||||||||||
|
Comment on lines
+125
to
+129
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Using If Consider failing explicitly if expected scripts are missing, or add verification that the scripts ran. 🛡️ Suggested verification approach - name: Critical validation suite
run: |
- pnpm run lint --if-present
- pnpm run typecheck --if-present
- pnpm run test --if-present
+ pnpm run lint
+ pnpm run typecheck
+ pnpm run testOr if some scripts are genuinely optional, add explicit checks: - name: Critical validation suite
run: |
+ # Verify critical scripts exist
+ jq -e '.scripts.lint and .scripts.typecheck and .scripts.test' package.json || { echo "Missing required scripts"; exit 1; }
pnpm run lint --if-present
pnpm run typecheck --if-present
pnpm run test --if-present📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||
|
|
||||||||||||||||||||||
| - name: Generate SBOM | ||||||||||||||||||||||
| if: steps.validate.outcome != 'success' | ||||||||||||||||||||||
| run: node scripts/generate_sbom.mjs || echo "SBOM generation failed" | ||||||||||||||||||||||
| - name: Publish merge-group summary | ||||||||||||||||||||||
| if: always() | ||||||||||||||||||||||
| run: | | ||||||||||||||||||||||
| echo "### Merge Queue Validation" >> "$GITHUB_STEP_SUMMARY" | ||||||||||||||||||||||
| echo "- Merge group SHA: ${{ github.event.merge_group.head_sha }}" >> "$GITHUB_STEP_SUMMARY" | ||||||||||||||||||||||
| echo "- Base ref: ${{ github.event.merge_group.base_ref }}" >> "$GITHUB_STEP_SUMMARY" | ||||||||||||||||||||||
| echo "- Trigger: ${{ github.event_name }}" >> "$GITHUB_STEP_SUMMARY" | ||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Adding
merge-queue / merge-queue-criticalas a required status check will likely block pull requests from entering the merge queue. GitHub's branch protection requires all mandatory status checks to pass before a pull request can be added to the queue. If this job is only triggered by themerge_groupevent (as suggested by the workflow name and PR description), it will not have run yet when a developer attempts to queue the PR. This creates a circular dependency where the PR cannot enter the queue because the check hasn't passed, and the check cannot run because the PR isn't in the queue. To resolve this, ensure the job (or a job with the same name) also triggers on standardpull_requestevents (e.g.,opened,synchronize), or consider if this check should be required only for the final merge and not for queue entry.