@@ -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