Languages: British English (en-GB) | 简体中文 (zh-CN)
- Copy the workflow in Workflow Usage to
.github/workflows/codex-review-gate.yml. - Use
JoeyTeng/codex-review-gate-action@v1, merge it to the default branch, then open a follow-up test PR. - After
codex/review-gatebehaves as expected, add it as a required status check. For recovery recipes, see the cookbook.
codex-review-gate is a reusable GitHub Action that owns a deterministic
codex/review-gate status check. It passes only from a complete evidence
snapshot when the latest official, trusted provider artifact is a
closed-grammar clean result bound to the current PR head and every
thread-backed Codex finding is resolved.
Target repositories keep a thin workflow at .github/workflows/codex-review-gate.yml; the review state machine lives in this action.
Note
This action requests and evaluates Codex generative AI review output. It keeps controlled @codex review marker comments minimal for reliable command parsing, and writes this disclosure to the GitHub Actions step summary when it requests a review. Codex may respond with AI-generated comments or reviews on the pull request. Review and verify AI-generated output before relying on it for security, correctness, or merge decisions.
The action itself does not execute pull request code. It coordinates GitHub comments, reviews, reactions, and commit statuses so repository maintainers can make Codex review a required branch-protection signal.
The runner implements event-driven evidence reconciliation with a serialized marker flow for requesting reviews:
- Runs under
pull_request_targetfrom the repository default branch. - Writes the configured commit status,
codex/review-gateby default, to the PR head SHA. - Passes only from a complete evidence snapshot when the latest official, trusted provider artifact that matches the closed clean grammar is bound to the current head and every historical thread-backed Codex finding is resolved.
- Treats
isOutdatedandisResolvedindependently. An outdated but unresolved thread still blocks the gate. - Recognises unthreaded top-level finding comments from exact repository and full-SHA blob links; the latest official, trusted closed-grammar clean artifact bound to the current head can supersede those findings.
- Validates official provider identity and binds reviews, inline comments, and top-level results to their reviewed commit.
- Accepts clean results only through a closed provider grammar; finding-shaped content takes precedence over a clean-looking lead or
APPROVEDstate. - Treats a configured provider's
Codex Reviewcomment, with an optional Markdown heading and emoji, as a broad terminal candidate. Exact one-linein progress/still in progressmessages are ignored, with an optional period or colon plus one to 160 metadata characters; a newer unknown candidate such ascompletedis malformed and fail-closed rather than silently ignored. - Rebuilds a complete evidence snapshot on every reconciliation. Historical
pendingorerrorstates, closed wait outcomes, and earlier incomplete API, pagination, identity, or commit parsing attempts are audit data, not sticky blockers. - Bounds each PR's evidence work to 64 MiB and 1,024 fetch attempts shared across snapshots and retries, with an 8 MiB streaming cap per response, 20,000 items per snapshot, and concurrency of four for HTTP and review-thread completion.
- Keeps a trusted sticky PR state comment with hidden metadata.
- Serializes controlled
@codex reviewmarker comments. - Keeps controlled marker comments minimal and writes the generative AI review disclosure to the GitHub Actions step summary.
- Treats Codex reactions as diagnostic signals only;
eyesreactions on the active marker comment count as liveness, not pass. - Uses scheduled or manual resume runs to retry unacknowledged or stalled markers.
- Fails closed when the current reconciliation cannot load or validate all required evidence. Transient exhaustion produces
pending; deterministic provider identity, schema, or commit conflicts produceerror. Both fail the workflow. - Reconciles complete review evidence before applying marker wait deadlines. Marker deadlines close or retry waits; they do not set an acceptance window for provider artifacts. A valid current-head clean artifact created after a marker deadline can pass on a later complete run.
- Uses sticky state, controlled markers, baselines, deadlines, recovery mode, and status history only for request orchestration, retry, liveness, audit, and idempotency. None of them authorises or rejects provider evidence.
- Before success, caches the newest same-context live status and its producer, revalidates PR lifecycle and head, loads the final complete snapshot with a bounded whole-snapshot orphan reload when needed, then deduplicates without another read and immediately posts success if required.
- Reasserts the computed status unless that newest same-context record already has the desired state and comes from exact
github-actions[bot]/Bot. An external or missing producer cannot expose an older trusted status as the deduplication candidate. - Migrates legacy marker and recovery fields for orchestration continuity without using them as provider-evidence authority.
- Accepts deprecated failed-findings recovery inputs for v1 interface compatibility; their values no longer change gate decisions or request orchestration.
- Evaluates official automatic-review and controlled-request output under the same identity, closed-grammar, current-head, and complete-snapshot rules.
action.yml: composite action wrapper for the runner.src/gate.mjs: GitHub Actions runner script.src/core.mjs: testable state and signal helpers.DESIGN.md: target signal model, state machine, and GHA cost model.COOKBOOK.md: normal operating path and failure recovery recipes.
For the event-driven review-gate design, state machine, automatic retry controls, GHA cost model, and recovery behaviour, see DESIGN.md. For operator recipes, see COOKBOOK.md.
The advanced design uses repository or organisation variables for controls that must take effect before a runner is allocated. For example, CODEX_REVIEW_GATE_AUTO_RETRY=false can skip scheduled retry jobs at the job if layer. Runtime env values are still useful for action behaviour after a job has started, but they cannot prevent GitHub Actions from assigning a runner.
The workflow example defaults to ubuntu-slim. Set CODEX_REVIEW_GATE_RUNNER_LABELS to a JSON array such as ["self-hosted","linux","x64","codex-review-gate"] to run the gate on a self-hosted runner.
name: Codex Review Gate
on:
pull_request_target:
types: [opened, reopened, synchronize, ready_for_review]
issue_comment:
types: [created]
pull_request_review:
types: [submitted]
pull_request_review_comment:
types: [created]
schedule:
- cron: "0 */2 * * *"
workflow_dispatch:
inputs:
pull_request:
description: Optional pull request number to gate
required: false
type: string
permissions:
contents: read
issues: write
pull-requests: write
statuses: write
concurrency:
group: codex-review-gate-${{ github.repository }}
cancel-in-progress: false
jobs:
codex-review-gate:
name: codex/review-gate runner
if: >-
${{
(github.event_name != 'schedule' || vars.CODEX_REVIEW_GATE_AUTO_RETRY != 'false') &&
(github.event_name != 'pull_request_target' ||
github.event.pull_request.user.login != 'dependabot[bot]') &&
(github.event_name != 'issue_comment' ||
github.event.issue.user.login != 'dependabot[bot]') &&
(github.event_name != 'pull_request_review' ||
github.event.pull_request.user.login != 'dependabot[bot]') &&
(github.event_name != 'pull_request_review_comment' ||
github.event.pull_request.user.login != 'dependabot[bot]') &&
(github.event_name != 'issue_comment' ||
(github.event.issue.pull_request &&
(contains(format(',chatgpt-codex-connector,chatgpt-codex-connector[bot],{0},',
vars.CODEX_REVIEW_GATE_BOT_LOGINS), format(',{0},', github.event.comment.user.login)) ||
contains(format(',chatgpt-codex-connector,chatgpt-codex-connector[bot],{0},',
vars.CODEX_REVIEW_GATE_BOT_LOGINS), format(', {0},', github.event.comment.user.login))))) &&
(github.event_name != 'pull_request_review' ||
(vars.CODEX_REVIEW_GATE_EVENT_MODE != 'comment-only' &&
github.event.pull_request.head.repo.full_name == github.event.pull_request.base.repo.full_name &&
(contains(format(',chatgpt-codex-connector,chatgpt-codex-connector[bot],{0},',
vars.CODEX_REVIEW_GATE_BOT_LOGINS), format(',{0},', github.event.review.user.login)) ||
contains(format(',chatgpt-codex-connector,chatgpt-codex-connector[bot],{0},',
vars.CODEX_REVIEW_GATE_BOT_LOGINS), format(', {0},', github.event.review.user.login))))) &&
(github.event_name != 'pull_request_review_comment' ||
(vars.CODEX_REVIEW_GATE_EVENT_MODE == 'full' &&
github.event.pull_request.head.repo.full_name == github.event.pull_request.base.repo.full_name &&
(contains(format(',chatgpt-codex-connector,chatgpt-codex-connector[bot],{0},',
vars.CODEX_REVIEW_GATE_BOT_LOGINS), format(',{0},', github.event.comment.user.login)) ||
contains(format(',chatgpt-codex-connector,chatgpt-codex-connector[bot],{0},',
vars.CODEX_REVIEW_GATE_BOT_LOGINS), format(', {0},', github.event.comment.user.login)))))
}}
runs-on: ${{ fromJSON(vars.CODEX_REVIEW_GATE_RUNNER_LABELS || '["ubuntu-slim"]') }}
timeout-minutes: 15
steps:
- uses: JoeyTeng/codex-review-gate-action@v1
with:
github-token: ${{ github.token }}
pull-request: ${{ github.event.pull_request.number || github.event.issue.number || github.event.inputs.pull_request }}
head-sha: ${{ github.event.pull_request.head.sha || '' }}
event-mode: ${{ vars.CODEX_REVIEW_GATE_EVENT_MODE }}
codex-bot-logins: ${{ vars.CODEX_REVIEW_GATE_BOT_LOGINS }}
completion-signal-buffer-seconds: ${{ vars.CODEX_REVIEW_GATE_COMPLETION_SIGNAL_BUFFER_SECONDS }}| Input | Default | Description |
|---|---|---|
github-token |
required | Token used to read PR review state, create comments, and write commit statuses. |
pull-request |
empty | Pull request number to gate. Leave empty for event payload routing or open-PR scans. |
head-sha |
empty | Deprecated compatibility input. Event-driven runs load the current PR head from GitHub. |
status-context |
codex/review-gate |
Commit status context written by the gate. |
state-marker |
codex-review-gate-state |
Hidden HTML marker used for the sticky state comment. |
marker-comment-marker |
codex-review-gate-marker |
Hidden HTML marker used for controlled Codex request comments. |
max-wait-seconds |
7200 |
Overall marker wait budget used for retry and liveness orchestration. |
marker-timeout-seconds |
3600 |
Time to wait for an acknowledged marker result before retrying. |
marker-ack-timeout-seconds |
300 |
Initial time to wait for Codex to acknowledge a marker before retrying. |
marker-ack-timeout-max-seconds |
1800 |
Maximum exponential backoff wait for unacknowledged markers. |
completion-signal-buffer-seconds |
30 |
Deprecated v1 interface-compatibility input. Accepted values no longer change gate decisions or request orchestration. |
failed-findings-recovery |
empty | Deprecated v1 interface-compatibility switch. Accepted values no longer change gate decisions or request orchestration. |
failed-findings-recovery-mode |
empty | Deprecated v1 interface-compatibility input. head and fresh no longer change gate decisions or request orchestration. |
event-mode |
empty | Event mode override: exactly standard, comment-only, or full. Empty falls back to CODEX_REVIEW_GATE_EVENT_MODE or standard. |
poll-interval-seconds |
30 |
Deprecated compatibility input. Event-driven runs do not poll. |
bootstrap-grace-seconds |
60 |
Deprecated compatibility input. Event-driven runs create controlled markers directly. |
bootstrap-timeout-seconds |
3600 |
Deprecated compatibility input. Bootstrap now closes after the grace period and starts a controlled marker. |
codex-bot-logins |
chatgpt-codex-connector,chatgpt-codex-connector[bot] |
Comma-separated GitHub logins accepted as Codex bot identities. |
trusted-comment-logins |
github-actions[bot] |
Comma-separated GitHub logins trusted for gate state and marker comments. |
After the workflow is merged into the default branch and has run at least once, add codex/review-gate to the repository ruleset as a required status check. Use GitHub Actions as the source because the workflow writes the status with GITHUB_TOKEN.
For new repositories, start from the language-neutral GitHub template repository
Joey-Tools/codex-gated-repo-template when you want the gate workflow
preinstalled. The source repository
JoeyTeng/codex-review-gate also ships templates/codex-gated-repo and a
dry-run bootstrap helper for creating or updating the required repository
ruleset:
node scripts/bootstrap-codex-review-gate.mjs --repo OWNER/REPO
node scripts/bootstrap-codex-review-gate.mjs --repo OWNER/REPO --applyRecommended rollout:
- Merge the workflow into the repository default branch.
- Open a follow-up test PR.
- Confirm the workflow creates a current-head marker comment on
openedandsynchronize. - Confirm the gate can pass or fail with the current runner implementation.
- Add
codex/review-gateto the ruleset required status checks.
Do not require codex/review-gate before the workflow exists on the protected default branch. The first PR that introduces the workflow cannot fully self-test the pull_request_target path because GitHub Actions reads that workflow from the repository default branch.
- The workflow does not execute PR code.
- The workflow should have both
issues: writeandpull-requests: writeso it can create PR conversation comments. - For the clearest request flow, repositories may disable Codex automatic review-on-push to reduce duplicate reviews. Automatic and controlled-marker results are evaluated by the same provider-evidence rules; the marker does not authorise either result.
- The runner fully paginates REST comments, reviews, inline comments, and GraphQL review threads before it can pass.
- Official REST evidence must come from an accepted Bot identity. Top-level issue comments also require the official
chatgpt-codex-connectorGitHub App by default. - REST evidence IDs must be positive safe integers; GraphQL opaque and
fullDatabaseIdfields must use their canonical string forms. Duplicate provider, review, inline-comment, or thread identities fail closed, including on resolved threads. - Reviews bind through the full
PullRequestReview.commit_id. Inline comments bind through their parent review andoriginal_commit_id, not GitHub's mutable relocatedcommit_id. - A reconciled inline comment lets its
COMMENTEDparent use the closed official inline-review wrapper without blob links in the wrapper body. The wrapper's reviewed-commit marker must still match the parent's fullcommit_id; unknown parent bodies fail closed. - Top-level clean comments bind through their reviewed-commit marker. A short marker must resolve uniquely through the repository commit API to the full current-head SHA.
- The closed clean structure requires the exact issue-comment lead and then permits either no tagline or one nonempty, trimmed, same-line presentation tagline separated by exactly one ASCII space and bounded to 160 UTF-16 code units. A tagline must be one known stem—
Nice work,Chef's kiss,What shall we delve into next,Already looking forward to the next diff,Keep them coming,Swish,Another round soon, please,Breezy,Can't wait for the next one,More of your lovely PRs please,Bravo,Keep it up,Delightful,Hooray, orYou're on a roll—plus exactly one final.,!, or?; exact:rocket:,:tada:, or:+1:; or one to eight exact RGI emoji graphemes, adjacent or separated by one ASCII space. Every unknown prose tagline fails closed, whether positive, actionable, or contradictory. The tagline is presentation only and never supplies clean or finding evidence. The comment still requires exactly one 10- or 40-hex reviewed-commit line and either no suffix or the exact official disclosure. AnAPPROVEDreview must be empty, exactLooks good., or have a unique exact finalNo findings.optionally after one summary of at most 240 characters. That summary must begin with exactCoverage:orReview coverage:and continue with a comma/and-separated list of backtick-wrapped identifier or path tokens matching[A-Za-z0-9_./:@+-]+, with only an optional final period; verb-led and other prose are rejected. A whole normalized target equal toP0–P3,S0–S3,critical,high,medium,low,finding,findings,blocker,blocking,found,detected,data-loss, orauth-bypassis rejected, but those words inside a real path or identifier are not blanket-rejected. Finding signals always win. - Review-body and unthreaded top-level findings must use exact
github.comlinks for the gated owner and repository with a full commit SHA. Unknown or conflicting current formats fail closed. - Resolve every thread-backed Codex finding before expecting success.
isOutdatedalone is not resolution. The latest official, trusted closed-grammar clean artifact bound to the current head can supersede an unthreaded top-level finding. - Ancestor checks validate the documented REST commit-comparison fields and their closed relationship/count matrix against the exact 40-hex
base...headrequest. The unpaginatedcommitslist must havemin(ahead_by, 250)unique full-SHA entries, exclude the base and merge-base commits, and bind its nonempty final entry to the requested head. Checks ignore undocumentedhead_commit, perform no extra head-commit GET, and fail closed on any schema or relationship contradiction. - Sticky state, controlled markers, baselines, deadlines, recovery mode, and
status history support request orchestration, retry, liveness, audit, and
idempotency only. They neither authorise nor reject provider evidence. A
rerun reconstructs current evidence and can reassert
successover a later stalependingorerrorstatus, including from a valid clean artifact created after an earlier marker deadline. - The optional status-deduplication GET is independent best-effort work: 100 statuses per page, at most 10 pages or 1,000 items, 1 MiB per response, 4 MiB total, and 16 fetch attempts. It selects the first (newest) same-context record before checking producer identity. Failure or exhaustion becomes
readFailed, does not taint review evidence, and causes the action to POST its computed status directly. - A review-evidence budget failure aborts active evidence requests. When concurrent loads fail differently, a deterministic non-
pendingerror, including a schema or identity conflict, wins over budget or transientpending. - Retryable REST and GraphQL responses honour valid
Retry-Afterdelays up to 10 seconds. Longer delays stop immediately, while missing or malformed values use bounded fallback retries; the header never expands the existing retry-safe method/status set. - Older short-SHA clean results are resolved lazily only when an older unthreaded finding's supersession depends on them.
- Evidence-budget exhaustion is transient: the action writes
pendingand exits non-zero. Deterministic provider schema, identity, or commit-binding conflicts writeerrorand also exit non-zero. - Default retry and liveness windows are currently 2 hours overall, 5 minutes for the first marker acknowledgement, 30 minutes maximum acknowledgement backoff capped by the marker timeout, and 1 hour before an acknowledged marker is treated as stalled. The recommended schedule example checks retry deadlines every 2 hours. These windows do not limit provider-artifact validity.
Use GitHub issues to report action bugs, bad gate behaviour, documentation gaps, or Marketplace listing issues. If a pull request receives problematic AI-generated review content, use GitHub's normal reporting and feedback tools for that specific comment or review, and include a link in an issue when it is relevant to this action's gate behaviour.
This repository is the Marketplace release package. Development, CI, and self-gating workflows are maintained in JoeyTeng/codex-review-gate.