11name : 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)
2+ # Posts a comment instructing the PR owner to add the "ready-for-aw" label
3+ # when Copilot review passes. This gates expensive agentic CI (smoke tests,
4+ # contribution check, etc.) to avoid wasting tokens on code that will change
5+ # after review.
56#
6- # This gates expensive agentic CI (smoke tests, contribution check, etc.)
7- # to avoid wasting tokens on code that will change after review.
7+ # Note: We cannot auto-add the label because GITHUB_TOKEN-added labels don't
8+ # trigger other workflows (GitHub's anti-cascade protection). The label must
9+ # be added manually by a human or a GitHub App.
810
911on :
1012 pull_request_review :
1113 types : [submitted]
1214 pull_request :
13- types : [synchronize] # push to PR branch
15+ types : [opened, synchronize]
1416
1517permissions :
1618 issues : write
@@ -21,16 +23,20 @@ jobs:
2123 gate :
2224 runs-on : ubuntu-latest
2325 steps :
24- - name : Check if ready for CI
26+ - name : Check if ready for agentic CI
2527 uses : actions/github-script@v7
2628 with :
2729 script : |
28- const copilotReviewers = new Set(['copilot', 'copilot[bot]', 'Copilot', 'copilot-pull-request-reviewer']);
30+ const LABEL = 'ready-for-aw';
31+ const COMMENT_MARKER = '<!-- ci-gate-bot -->';
32+ const copilotReviewers = new Set(['copilot', 'copilot[bot]', 'Copilot', 'copilot-pull-request-reviewer', 'copilot-pull-request-reviewer[bot]']);
2933 const isCopilotReviewer = login => copilotReviewers.has(login ?? '');
3034 const { owner, repo } = context.repo;
3135 const pr_number = context.payload.pull_request?.number
3236 || context.payload.review?.pull_request?.number;
3337
38+ core.info(`Event: ${context.eventName}, PR: ${pr_number || 'none'}`);
39+
3440 if (!pr_number) {
3541 core.info('No PR number found, skipping');
3642 return;
@@ -40,61 +46,74 @@ jobs:
4046 const { data: labels } = await github.rest.issues.listLabelsOnIssue({
4147 owner, repo, issue_number: pr_number
4248 });
43- if (labels.some(l => l.name === 'ready-for-ci' )) {
49+ if (labels.some(l => l.name === LABEL )) {
4450 core.info('Label already present, skipping');
4551 return;
4652 }
4753
48- // For synchronize events (push after review), add label if review exists
54+ // Helper: post or update the gate comment
55+ async function postGateComment(message) {
56+ const { data: comments } = await github.rest.issues.listComments({
57+ owner, repo, issue_number: pr_number
58+ });
59+ const existing = comments.find(c => c.body?.includes(COMMENT_MARKER));
60+ const body = `${COMMENT_MARKER}\n${message}`;
61+ if (existing) {
62+ await github.rest.issues.updateComment({ owner, repo, comment_id: existing.id, body });
63+ } else {
64+ await github.rest.issues.createComment({ owner, repo, issue_number: pr_number, body });
65+ }
66+ }
67+
68+ // Determine if Copilot has reviewed
69+ let copilotReviewed = false;
70+ let hasInlineComments = false;
71+
4972 if (context.eventName === 'pull_request') {
5073 const { data: reviews } = await github.rest.pulls.listReviews({
5174 owner, repo, pull_number: pr_number
5275 });
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;
76+ const reviewLogins = reviews.map(r => r.user?.login);
77+ core.info(`Found ${reviews.length} review(s): ${reviewLogins.join(', ')}`);
78+ copilotReviewed = reviews.some(r => isCopilotReviewer(r.user?.login));
6279 }
6380
64- // For review events, check if review has actionable comments
6581 if (context.eventName === 'pull_request_review') {
6682 const reviewer = context.payload.review?.user?.login;
83+ core.info(`Review event from: ${reviewer}`);
6784 if (!isCopilotReviewer(reviewer)) {
6885 core.info(`Review from ${reviewer}, not Copilot — skipping`);
6986 return;
7087 }
88+ copilotReviewed = true;
7189
72- // Check for unresolved review comments on this PR
90+ // Check for inline comments
7391 const { data: comments } = await github.rest.pulls.listReviewComments({
7492 owner, repo, pull_number: pr_number
7593 });
7694 const copilotComments = comments.filter(c =>
77- isCopilotReviewer(c.user?.login)
78- && !c.in_reply_to_id
95+ isCopilotReviewer(c.user?.login) && !c.in_reply_to_id
7996 );
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 {
97+ hasInlineComments = copilotComments.length > 0;
98+ if (hasInlineComments) {
8899 core.info(`Copilot left ${copilotComments.length} inline comment(s), waiting for fixes`);
89100 }
90101 }
91102
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');
103+ if (!copilotReviewed) {
104+ core.info('No Copilot review found yet, waiting');
105+ return;
106+ }
107+
108+ if (hasInlineComments) {
109+ await postGateComment(
110+ '⏳ Copilot review has inline comments. Address the feedback, then add the `ready-for-aw` label to trigger agentic CI.'
111+ );
112+ return;
113+ }
114+
115+ // Copilot reviewed with no inline comments — prompt to add label
116+ await postGateComment(
117+ '✅ Copilot review passed. Add the `ready-for-aw` label to trigger agentic CI smoke tests.'
118+ );
119+ core.info('Posted comment instructing PR owner to add ready-for-aw label');
0 commit comments