Skip to content

Commit eed40e4

Browse files
lpcoxCopilotCopilot
authored
feat: gate agentic CI behind ready-for-ci label (#5054)
* feat: gate agentic CI behind ready-for-ci label Add a ci-gate.yml workflow that auto-applies the 'ready-for-ci' label after Copilot review completes with no inline feedback, or when a push is made after review feedback has been addressed. All 14 agentic workflows (build-test, contribution-check, security-guard, smoke-*) now use label_command trigger instead of pull_request, so they only run once the label is present. This prevents token waste from expensive agentic CI running while review is still in progress, only to be invalidated by review feedback. Tier 1 CI (lint, CodeQL, pr-title, dependency-audit) still runs immediately. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * feat: gate integration tests behind ready-for-ci label The integration test suite (5 jobs, 45-min timeout each) now only runs on PRs when the ready-for-ci label is applied. Push to main and workflow_dispatch still trigger unconditionally. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix: ready-for-ci workflow guards --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
1 parent 5b59ced commit eed40e4

32 files changed

Lines changed: 969 additions & 294 deletions

.github/workflows/build-test.lock.yml

Lines changed: 66 additions & 17 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

.github/workflows/build-test.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,10 @@ description: Build Test Suite
33
on:
44
roles: all
55
workflow_dispatch:
6-
pull_request:
7-
types: [opened, synchronize, reopened]
6+
label_command:
7+
name: ready-for-ci
8+
events: [pull_request]
9+
remove_label: false
810
permissions:
911
contents: read
1012
pull-requests: read

.github/workflows/ci-gate.yml

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
name: CI Gate
2+
# Automatically adds the "ready-for-ci" label to PRs when:
3+
# 1. Copilot review completes with no actionable feedback, OR
4+
# 2. A push is made after Copilot review (indicating feedback was addressed)
5+
#
6+
# This gates expensive agentic CI (smoke tests, contribution check, etc.)
7+
# to avoid wasting tokens on code that will change after review.
8+
9+
on:
10+
pull_request_review:
11+
types: [submitted]
12+
pull_request:
13+
types: [synchronize] # push to PR branch
14+
15+
permissions:
16+
issues: write
17+
pull-requests: write
18+
contents: read
19+
20+
jobs:
21+
gate:
22+
runs-on: ubuntu-latest
23+
steps:
24+
- name: Check if ready for CI
25+
uses: actions/github-script@v7
26+
with:
27+
script: |
28+
const copilotReviewers = new Set(['copilot', 'copilot[bot]', 'Copilot']);
29+
const isCopilotReviewer = login => copilotReviewers.has(login ?? '');
30+
const { owner, repo } = context.repo;
31+
const pr_number = context.payload.pull_request?.number
32+
|| context.payload.review?.pull_request?.number;
33+
34+
if (!pr_number) {
35+
core.info('No PR number found, skipping');
36+
return;
37+
}
38+
39+
// Check if label already exists
40+
const { data: labels } = await github.rest.issues.listLabelsOnIssue({
41+
owner, repo, issue_number: pr_number
42+
});
43+
if (labels.some(l => l.name === 'ready-for-ci')) {
44+
core.info('Label already present, skipping');
45+
return;
46+
}
47+
48+
// For synchronize events (push after review), add label if review exists
49+
if (context.eventName === 'pull_request') {
50+
const { data: reviews } = await github.rest.pulls.listReviews({
51+
owner, repo, pull_number: pr_number
52+
});
53+
const hasCopilotReview = reviews.some(r => isCopilotReviewer(r.user?.login));
54+
if (hasCopilotReview) {
55+
core.info('Post-review push detected, adding ready-for-ci label');
56+
await github.rest.issues.addLabels({
57+
owner, repo, issue_number: pr_number,
58+
labels: ['ready-for-ci']
59+
});
60+
}
61+
return;
62+
}
63+
64+
// For review events, check if review has actionable comments
65+
if (context.eventName === 'pull_request_review') {
66+
const reviewer = context.payload.review?.user?.login;
67+
if (!isCopilotReviewer(reviewer)) {
68+
core.info(`Review from ${reviewer}, not Copilot — skipping`);
69+
return;
70+
}
71+
72+
// Check for unresolved review comments on this PR
73+
const { data: comments } = await github.rest.pulls.listReviewComments({
74+
owner, repo, pull_number: pr_number
75+
});
76+
const copilotComments = comments.filter(c =>
77+
isCopilotReviewer(c.user?.login)
78+
&& !c.in_reply_to_id
79+
);
80+
81+
if (copilotComments.length === 0) {
82+
core.info('Copilot review with no inline comments, adding ready-for-ci label');
83+
await github.rest.issues.addLabels({
84+
owner, repo, issue_number: pr_number,
85+
labels: ['ready-for-ci']
86+
});
87+
} else {
88+
core.info(`Copilot left ${copilotComments.length} inline comment(s), waiting for fixes`);
89+
}
90+
}
91+
92+
- name: Remove label on new push (reset gate)
93+
if: github.event_name == 'pull_request' && github.event.action == 'synchronize'
94+
uses: actions/github-script@v7
95+
with:
96+
script: |
97+
// This step intentionally does NOT remove the label on push.
98+
// The label persists so that re-triggered agentic workflows can run.
99+
// The label is only meaningful as a "review addressed" signal.
100+
core.info('Label preserved across pushes to allow agentic CI to re-run');

0 commit comments

Comments
 (0)