Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ Keep GitHub transport and pagination in `src/github.ts`, provider interpretation

- When changing `src/`, rebuild and commit `dist/index.js` and `dist/index.js.map` in the same change.
- When changing the consumer workflow pin, use a reviewed full-length commit SHA; do not replace it with a mutable branch reference.
- Never write a literal `@codex` in a pull request description, even inside a code span: the connector treats it as a Codex task request and skips the automatic review for that PR. Write "the Codex review request comment" or `@ codex review` instead.
- Pin-only updates that advance the dogfood workflow and example to a merged commit may be pushed directly to `main` without a pull request.
- When changing review orchestration, keep the Action read-only: never post `@codex review`, resolve conversations, or mutate pull request state.
- When adding TypeScript abstractions, prefer deep cohesive functions over chains of thin one-use helpers; keep substantial functions long enough to own a complete responsibility.
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ When `gh pr checks` reports a failure, the failure log line gives the agent one
```console
$ gh run view --log-failed
...
##[error]No Codex review signal for current HEAD 821c759c9c3256e8a677800dbe8e4a88ae5e2ab0. Run: gh pr comment 42 --body '@codex review' — then re-run: gh run rerun 33499862152 --failed
##[error]No Codex review signal for current HEAD 821c759c9c3256e8a677800dbe8e4a88ae5e2ab0. Request one and run these back to back, without waiting in between: gh pr comment 42 --body '@codex review' && gh run rerun 33499862152 --failed && gh pr checks 42 --watch. The re-run check itself waits up to 30 min for the Codex review to finish, then passes or reports the next action. Start it right away and wait on the check; read Codex reviews or comments directly only if the check result looks wrong.
```

The job summary mirrors the same guidance in the web UI for human debugging.
Expand Down
12 changes: 6 additions & 6 deletions dist/index.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/index.js.map

Large diffs are not rendered by default.

7 changes: 4 additions & 3 deletions docs/CONFIGURATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,11 +86,12 @@ The Action infers this number from pull request, review, review-comment, and pul

## Reruns

The missing-review failure prints both commands in the job log and the job summary:
Every failure prints its next steps as one chain in the failure log line and as a command block in the job summary. The missing-review failure, for example, prints:

```shell
gh pr comment 42 --body '@codex review'
gh run rerun RUN_ID --failed
gh pr comment 42 --body '@codex review' && gh run rerun RUN_ID --failed && gh pr checks 42 --watch
```

Run the commands back to back. The rerun attempt is the step that waits for Codex: it polls for up to `review-timeout-seconds`, which the message states, and then passes or prints the next action. `gh pr checks --watch` waits on that attempt. Post the review request before the rerun; a request comment newer than every Codex artifact keeps the attempt waiting until the review starts. Reading Codex reviews or comments directly remains useful when the check result looks wrong.

A rerun reconstructs the PR state and reads its live `head.sha`, reviews, comments, reactions, and threads. It does not depend on a resolve webhook. GitHub still executes the Action version pinned by the original workflow run; update the workflow pin and trigger a new run when adopting a newer Action release.
24 changes: 24 additions & 0 deletions docs/adr/0009-the-rerun-is-the-waiter.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# ADR 0009: The rerun is the waiter

- Status: Accepted
- Date: 2026-10-11

## Context

ADR 0002 makes the running attempt the sole Codex lifecycle coordinator, and failures told agents to "request a fresh review — then re-run". An observed session with a weaker model read the second "then" as "after the review appears": it posted `@codex review`, then wrote its own `until gh api …/reviews …; do sleep 15; done` loop for the new HEAD, and only reran the check after being challenged. The model treated the check as a one-shot snapshot evaluator, so it waited for the input to exist before re-evaluating. Nothing in the message said the rerun itself waits.

The `review-timeout` message made this worse by literally saying "Re-run after the review finishes".

## Decision

Every failure prints its next steps as one `&&` chain in run order: the optional review request, `gh run rerun RUN_ID --failed`, and `gh pr checks PR --watch`. The message then states that the rerun waits up to the configured `review-timeout-seconds` for the Codex review to finish, to start it right away, and to wait on the check.

The message recommends rather than forbids. Reading Codex reviews or comments directly stays available when the check result looks wrong, so a check bug does not trap the agent.

`gh pr checks --watch` is guidance, not a correctness requirement: the attempt alone decides whether the review is accepted. If the watch races the rerun and reports the previous attempt, the agent loses only time.

## Consequences

- `failureOutput` and `summaryMarkdown` take a `MessageOptions` record that carries the run id, review-hint policy, LGTM mode, and review timeout.
- A `review-timeout` failure tells the agent to rerun to keep waiting, not to wait outside the check.
- The chain is longer than the previous two-step instruction; message fixtures pin the full text.
53 changes: 19 additions & 34 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import * as github from "@actions/github";
import { loadReviewSnapshot } from "./github.js";
import {
failureOutput,
type MessageOptions,
reviewRequestCommand,
suggestsReviewRequest,
summaryMarkdown,
Expand All @@ -28,29 +29,20 @@ async function publishResult(
evaluation: ReviewEvaluation,
state: "success" | "failure",
reason: string,
reviewHintPolicy: ReviewHintPolicy,
requireLgtm: boolean,
messageOptions: MessageOptions,
): Promise<void> {
core.setOutput("state", state);
core.setOutput("reason", reason);
core.setOutput("head-sha", snapshot.headSha);
core.setOutput("review-signal", evaluation.signal);
core.setOutput("unresolved-count", evaluation.unresolvedThreads.length.toString());
const suggestReviewRequest = suggestsReviewRequest(reason, evaluation, reviewHintPolicy);
const suggestReviewRequest = suggestsReviewRequest(reason, evaluation, messageOptions.reviewHint);
core.setOutput(
"review-hint",
suggestReviewRequest ? reviewRequestCommand(snapshot.pullRequest) : "",
);
core.summary.addRaw(
summaryMarkdown(
snapshot,
evaluation,
state,
reason,
process.env.GITHUB_RUN_ID,
reviewHintPolicy,
requireLgtm,
),
summaryMarkdown(snapshot, evaluation, state, reason, messageOptions),
);
await core.summary.write();
}
Expand Down Expand Up @@ -113,6 +105,12 @@ async function run(): Promise<void> {
throw new Error("codex-bot-logins must contain at least one login.");
}

const messageOptions: MessageOptions = {
runId: process.env.GITHUB_RUN_ID,
reviewHint: config.reviewHintPolicy,
requireLgtm: config.requireLgtm,
reviewTimeoutSeconds: config.reviewTimeoutSeconds,
};
let headStartedAt = Date.now();
let observedHeadSha = "";
let livenessSeen = false;
Expand Down Expand Up @@ -145,9 +143,7 @@ async function run(): Promise<void> {
"unresolved-threads",
snapshot,
evaluation,
process.env.GITHUB_RUN_ID,
config.reviewHintPolicy,
config.requireLgtm,
messageOptions,
);
for (const line of output.logLines) {
core.info(line);
Expand All @@ -157,8 +153,7 @@ async function run(): Promise<void> {
evaluation,
"failure",
"unresolved-threads",
config.reviewHintPolicy,
config.requireLgtm,
messageOptions,
);
core.setFailed(output.annotation);
return;
Expand All @@ -182,8 +177,7 @@ async function run(): Promise<void> {
evaluation,
"success",
"ready",
config.reviewHintPolicy,
config.requireLgtm,
messageOptions,
);
return;
}
Expand Down Expand Up @@ -214,17 +208,14 @@ async function run(): Promise<void> {
evaluation,
"failure",
"lgtm-missing",
config.reviewHintPolicy,
config.requireLgtm,
messageOptions,
);
core.setFailed(
failureOutput(
"lgtm-missing",
snapshot,
evaluation,
process.env.GITHUB_RUN_ID,
config.reviewHintPolicy,
config.requireLgtm,
messageOptions,
).annotation,
);
return;
Expand All @@ -240,17 +231,14 @@ async function run(): Promise<void> {
evaluation,
"failure",
"review-missing",
config.reviewHintPolicy,
config.requireLgtm,
messageOptions,
);
core.setFailed(
failureOutput(
"review-missing",
snapshot,
evaluation,
process.env.GITHUB_RUN_ID,
config.reviewHintPolicy,
config.requireLgtm,
messageOptions,
).annotation,
);
return;
Expand All @@ -265,17 +253,14 @@ async function run(): Promise<void> {
evaluation,
"failure",
"review-timeout",
config.reviewHintPolicy,
config.requireLgtm,
messageOptions,
);
core.setFailed(
failureOutput(
"review-timeout",
snapshot,
evaluation,
process.env.GITHUB_RUN_ID,
config.reviewHintPolicy,
config.requireLgtm,
messageOptions,
).annotation,
);
return;
Expand Down
Loading
Loading