Skip to content

Commit fac2f87

Browse files
authored
ci: Correlate Codex reviews with base updates
1 parent 1bbf40c commit fac2f87

1 file changed

Lines changed: 61 additions & 10 deletions

File tree

‎.github/workflows/codex-review-gate.yml‎

Lines changed: 61 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -224,7 +224,7 @@ jobs:
224224
.sort((a, b) => b.id - a.id)[0];
225225
}
226226
227-
async function createPendingGate(pull) {
227+
async function createPendingGate(pull, requireFreshRequest = false) {
228228
const created = await github.rest.checks.create({
229229
owner,
230230
repo,
@@ -235,17 +235,39 @@ jobs:
235235
started_at: now.toISOString(),
236236
details_url: `${context.serverUrl}/${owner}/${repo}/pull/${pull.number}`,
237237
output: {
238-
title: 'Codex review pending',
239-
summary:
240-
'Waiting for managed Codex to review this pull request commit.',
238+
title: requireFreshRequest
239+
? 'Codex review required after base update'
240+
: 'Codex review pending',
241+
summary: requireFreshRequest
242+
? 'The pull request base changed. A fresh managed Codex review must be requested before this gate can complete.'
243+
: 'Waiting for managed Codex to review this pull request commit.',
241244
},
242245
});
243246
return created.data;
244247
}
245248
246-
async function inspectCodexOutcome(pull, run, quotaFloor) {
249+
function requestedAtForRun(run) {
250+
const summary = String(run.output?.summary || '');
251+
const match = summary.match(/^Requested at: (.+)$/m);
252+
return match ? asDate(match[1]) : null;
253+
}
254+
255+
async function inspectCodexOutcome(
256+
pull,
257+
run,
258+
quotaFloor,
259+
allowTerminal,
260+
terminalFloor,
261+
) {
247262
const startedAt = asDate(run.started_at || run.created_at) || now;
248-
const floor = quotaFloor && quotaFloor > startedAt ? quotaFloor : startedAt;
263+
const resultFloor =
264+
terminalFloor && terminalFloor > startedAt
265+
? terminalFloor
266+
: startedAt;
267+
const floor =
268+
quotaFloor && quotaFloor > resultFloor
269+
? quotaFloor
270+
: resultFloor;
249271
250272
const [reviews, reviewComments, issueComments] =
251273
await Promise.all([
@@ -281,13 +303,19 @@ jobs:
281303
282304
const terminal = [];
283305
for (const review of reviews) {
306+
if (!allowTerminal) continue;
284307
if (!isCodex(review.user?.login)) continue;
285308
if (review.commit_id !== pull.head.sha) continue;
286309
const submittedAt = asDate(review.submitted_at);
287310
const isTriggeringReview =
288311
eventPullNumber === pull.number &&
289312
triggeringReviewId === Number(review.id);
290-
if (!submittedAt || (!isTriggeringReview && submittedAt < startedAt)) {
313+
const triggerMayPrecedeFloor =
314+
isTriggeringReview && !terminalFloor;
315+
if (
316+
!submittedAt ||
317+
(submittedAt < resultFloor && !triggerMayPrecedeFloor)
318+
) {
291319
continue;
292320
}
293321
const findingCount = commentsByReview.get(Number(review.id)) || 0;
@@ -301,6 +329,7 @@ jobs:
301329
}
302330
303331
for (const comment of issueComments) {
332+
if (!allowTerminal) continue;
304333
if (!isCodex(comment.user?.login)) continue;
305334
const body = String(comment.body || '');
306335
if (!body.startsWith("Codex Review: Didn't find any major issues.")) {
@@ -338,7 +367,12 @@ jobs:
338367
const isTriggeringComment =
339368
eventPullNumber === pull.number &&
340369
triggeringIssueCommentId === Number(comment.id);
341-
if (!createdAt || (!isTriggeringComment && createdAt < startedAt)) {
370+
const triggerMayPrecedeFloor =
371+
isTriggeringComment && !terminalFloor;
372+
if (
373+
!createdAt ||
374+
(createdAt < resultFloor && !triggerMayPrecedeFloor)
375+
) {
342376
continue;
343377
}
344378
terminal.push({ at: createdAt, kind: 'approve', findingCount: 0 });
@@ -395,18 +429,25 @@ jobs:
395429
run && run.external_id !== pull.base.sha,
396430
);
397431
if (baseChanged) run = undefined;
398-
if (!run) run = await createPendingGate(pull);
432+
if (!run) run = await createPendingGate(pull, baseChanged);
399433
400434
if (run.status === 'completed') continue;
401435
436+
const requestFloor = requestedAtForRun(run);
437+
const requiresFreshRequest =
438+
run.output?.title === 'Codex review required after base update';
439+
402440
const outcome = await inspectCodexOutcome(
403441
pull,
404442
run,
405443
availableSince,
444+
!requiresFreshRequest || Boolean(requestFloor),
445+
requestFloor,
406446
);
407447
if (outcome.terminal) {
408448
terminalEvents.push({
409449
...outcome.terminal,
450+
base_sha: pull.base.sha,
410451
check_run_id: run.id,
411452
head_sha: pull.head.sha,
412453
pr: pull.number,
@@ -421,6 +462,7 @@ jobs:
421462
head_sha: pull.head.sha,
422463
needs_manual:
423464
baseChanged ||
465+
requiresFreshRequest ||
424466
context.eventName === 'pull_request_target' &&
425467
context.payload.pull_request?.number === pull.number &&
426468
(context.payload.action === 'synchronize' ||
@@ -481,9 +523,15 @@ jobs:
481523
.sort((a, b) => b.at - a.at)[0];
482524
483525
if (latestTerminal && (!latestQuota || latestTerminal.at >= latestQuota.at)) {
526+
const terminalMatchesProbe =
527+
state.probe &&
528+
latestTerminal.pr === state.probe.pr &&
529+
latestTerminal.head_sha === state.probe.head_sha &&
530+
latestTerminal.base_sha === state.probe.base_sha;
484531
state = {
485532
...emptyState,
486533
available_since: latestTerminal.at.toISOString(),
534+
probe: terminalMatchesProbe ? null : state.probe,
487535
};
488536
} else if (latestQuota) {
489537
const probeRequestedAt = asDate(state.probe?.requested_at);
@@ -584,8 +632,11 @@ jobs:
584632
status: 'in_progress',
585633
output: {
586634
title: 'Codex review requested',
587-
summary:
635+
summary: [
636+
`Requested at: ${now.toISOString()}`,
637+
`Base commit: ${candidate.base_sha}`,
588638
'Requested managed Codex review and waiting for its result.',
639+
].join('\n'),
589640
},
590641
});
591642
}

0 commit comments

Comments
 (0)