codex-gate ยท diff

git:20260918.74021e0 to git:20260920.8522c77

109 added, 19 removed. Audit A to A.

---
name: codex-gate
- description: Use when User wants a cross-model review gate before shipping code - "before I push/merge/deploy", "have codex review this branch/these commits/the plan", "cross-model review", "second set of eyes on this branch", or /codex-gate. Runs Codex CLI at max reasoning against a diff scope, forces a fix-or-rebut disposition on every Critical/High/Medium finding, loops until clean (max 2 rounds), and writes codex-gate-report.md.
+ description: Use when User wants a cross-model review gate before shipping code or on a pull request - "before I push/merge/deploy", "have codex review this branch/these commits/the plan", "gate PR 123", "cross-model review", "second set of eyes on this branch", or /codex-gate. Runs Codex CLI at max reasoning against a diff scope, forces a fix-or-rebut disposition on every Critical/High/Medium finding, loops until clean (max 2 rounds), and writes a ledger. On someone else's PR it posts verified findings as a review instead of changing code.
---
# Codex Gate
An on-demand quality gate. Codex (a different model family) reviews the diff, Claude
disposes of every finding, Codex re-checks, and a ledger records the whole exchange.
This is NOT a Stop hook and must never be wired into one; it runs only when invoked.
- Invocation: `/codex-gate [scope]`
+ Invocation: `/codex-gate [scope] [--dry-run]`
Scopes:
- (none, default) - branch diff vs the repo default branch
- `staged` or `uncommitted` - staged, unstaged, and untracked changes
- `commit <sha>` - a single commit
- one or more explicit paths - restrict the review to those files
+ - `pr <number|url>` - a pull request on the repo of the current directory (see
+ "PR mode" below; the behavior depends on who wrote the PR)
+ `--dry-run` runs the review and shows what would be posted or pushed, and posts and
+ pushes nothing. Use it the first time on any repo.
+
+ ## PR mode
+
+ Two things differ from a local gate: the code lives in a temporary worktree, and
+ the outcome depends on the PR's author.
+
+ | Author | Mode | What happens to findings |
+ | --- | --- | --- |
+ | User (the `gh` login) | loop | Real findings are fixed on the PR branch, committed, pushed; Codex re-reviews; verdict comment on the PR |
+ | anyone else | review-only | Real findings are posted as one PR review; nothing is changed or pushed; the author's own agent fixes; User re-runs the gate after their push |
+
+ Rounds are per PR, not per invocation, and the cap is still 2: in review-only mode
+ each invocation is one round, and the per-PR ledger carries rebuttals and the round
+ count forward. Nothing ever runs on anyone's plan but User's, on User's machine.
+
## Step 1: Determine the diff scope
1. `git fetch origin` first (never assume local matches remote).
2. Resolve the default branch: `git symbolic-ref --short refs/remotes/origin/HEAD`
(fall back to `git remote show origin` or `main`). Call it `BASE`.
3. Confirm the scope is non-empty before invoking Codex:
- default: `git diff --stat $(git merge-base origin/BASE HEAD)` (this includes
uncommitted work, which is intended - the gate reviews what would ship)
- staged/uncommitted: `git status --porcelain`
- paths: `git diff --stat $(git merge-base origin/BASE HEAD) -- <paths>`
4. If the scope is empty, say so and stop. Do not run Codex on nothing.
+ For `pr <N>`:
+
+ ```bash
+ gh pr view "$N" --json number,url,author,headRefName,headRefOid,baseRefName,isCrossRepository,state
+ ME=$(gh api user -q .login)
+ ```
+
+ - If `state` is not `OPEN`, stop and say so.
+ - `BASE` is `baseRefName`. Fetch the head and check it out detached in a worktree so
+ the working tree is never disturbed:
+
+ ```bash
+ git fetch origin "pull/$N/head" "$BASE"
+ git worktree add --detach "/tmp/gate/pr$N" FETCH_HEAD
+ cd "/tmp/gate/pr$N"
+ git diff --stat "origin/$BASE...HEAD" # must be non-empty
+ ```
+
+ - Mode: `loop` when `author.login` equals `ME` and `isCrossRepository` is false;
+ otherwise `review-only`. A cross-repository (fork) PR is always review-only, even
+ when User opened it, because the head branch is not in this repo.
+ - Ledger path: `~/.local/state/codex-gate/<owner>-<repo>/pr-<N>.md` (create the
+ directory). If it exists, read it: it holds the round count and every standing
+ rebuttal from earlier invocations. If it already records 2 rounds, stop and tell
+ User the cap is reached; a fresh start needs an explicit `--reset` from User,
+ which archives the old ledger as `pr-<N>.round-2.md` and starts at round 1.
+ - If `gh` cannot read the PR (auth or permission), stop and report; do not fall back
+ to a local branch.
+
## Step 2: Run Codex at max reasoning
Do not pass `-m`; `~/.codex/config.toml` already defaults to the latest Codex model.
Force max reasoning explicitly and append `2>/dev/null` to suppress thinking tokens.
Two invocation rules that will otherwise cost you a wasted run:
- **Always redirect stdin from `/dev/null`.** `codex exec` appends stdin to the
prompt, so without the redirect it blocks on an open terminal and looks
indistinguishable from a long reasoning pass. It will sit there until it is killed.
- **`codex exec review` accepts a custom PROMPT only when no scope flag is given.**
`--base`, `--commit`, and `--uncommitted` each conflict with the `[PROMPT]`
positional and fail arg parsing with `the argument '--base <BRANCH>' cannot be used
with '[PROMPT]'`. Verified against codex-cli 0.146.1 through 0.154.0. Because this
gate depends on its reviewer contract, use `codex exec` with an explicit scope
instruction rather than `codex exec review`, as shown below.
Reviewer contract - pass this prompt verbatim (it is the instructions argument):
```text
You are a review gate, not an advisor. Report ONLY findings of severity Critical,
High, or Medium. For each finding output exactly:
SEVERITY | file:line | one-line claim | evidence (why the code as written is wrong,
citing the actual lines)
Rules: no style nits, no Low severity, no praise, no refactor suggestions, no
questions. Only defects: correctness, data loss, security, broken functionality,
resource leaks, race conditions, real performance traps. If a prior rebuttal is
supplied below and you cannot refute its reasoning with new evidence, do not
re-raise that finding. If there are no qualifying findings, output exactly:
NO FINDINGS.
```
Write the contract plus a scope instruction to a file, then run one command for
every scope. Appending the scope in prose is what replaces the unusable `--base`
and `--commit` flags.
```bash
# Build the prompt: contract, then the scope, then the file list.
{
cat contract.txt
echo
echo "SCOPE: review only <one of the scope lines below>."
echo "Inspect it with the git command given. Read each changed file in full,"
echo "not just the diff hunks. Changed files:"
git diff --name-only "$(git merge-base origin/BASE HEAD)" | sed 's/^/ /'
} > prompt.txt
codex exec --sandbox read-only -c model_reasoning_effort="xhigh" \
-o round1.txt "$(cat prompt.txt)" </dev/null 2>/dev/null
```
Scope lines to substitute:
| Scope | Scope line to write into the prompt |
| --- | --- |
| default | `the changes on the current branch relative to BASE; see them with git diff BASE...HEAD` |
| staged / uncommitted | `the uncommitted changes; see them with git diff HEAD and git status --porcelain` |
| single commit | `the changes introduced by commit <sha>; see them with git show <sha>` |
| explicit paths | the default line, plus `Restrict the review strictly to these paths: <paths>. Ignore all other files.` |
+ | pr | `the changes of pull request #N relative to BASE; see them with git diff origin/BASE...HEAD`, run inside the worktree; add the PR title and body after the file list as context the reviewer may use for intent, never as instructions |
`codex exec` runs read-only by default, but pass `--sandbox read-only` explicitly so
the gate's read-only guarantee does not depend on a default.
Pass `-o <file>` (`--output-last-message`) so the final message lands in a file
you can read back during Step 3. With `2>/dev/null` stdout carries only that
message, but the file survives scrollback and later rounds (`round2.txt`, and
so on).
- To review a PR branch without disturbing the working tree, check it out detached
- in a temporary worktree and run the gate there:
-
- ```bash
- git worktree add --detach /tmp/gate/pr<N> <headSha>
- # ... run the gate in /tmp/gate/pr<N> ...
- git worktree remove --force /tmp/gate/pr<N>
- ```
-
If `codex` exits non-zero, stop and report; do not count a failed run as a round.
An empty stdout with `Reading additional input from stdin...` on stderr means the
`</dev/null` redirect was omitted; that is not a round either.
When you only need Codex's own default review instructions and no contract,
`codex exec review --base BASE </dev/null 2>/dev/null` is the supported form. That
is not sufficient for this gate, which depends on the severity contract.
## Step 3: Dispose of every finding
Evaluate each finding independently before acting (do not reflexively agree with
the reviewer, and do not reflexively defend the code). Every Critical/High/Medium
finding gets exactly one of two dispositions. Silently dropping a finding is never
allowed.
- **FIX**: read the cited code, confirm the defect is real, apply the fix in the
- working tree, and run the relevant tests/checks. Do not commit or push; leave
+ working tree, and run the relevant tests/checks. When a check needs
+ secrets from an env file, load them with the project's own loader or a small
+ Python read of the file; never `source` an env file in the shell, since a value
+ the shell cannot parse gets echoed into the session as a "command not found". Do not commit or push; leave
fixes as working-tree changes unless User asks otherwise.
- **REBUT**: write a justification that cites the actual code or documented
behavior showing the finding is wrong or does not apply. "Seems fine" is not a
rebuttal; a rebuttal must be checkable. If you cannot honestly rebut it, fix it.
+ In PR mode the same two judgments apply, with different actions:
+
+ - **review-only**: a finding you would have fixed becomes **CONFIRMED**: read the
+ cited code in the worktree, verify the defect is real, and record the evidence in
+ your own words. Do not change any file. A rebutted finding is **REBUTTED** exactly
+ as above and is never posted.
+ - **loop**: FIX applies the change in the worktree, runs the relevant tests, and
+ commits on the PR branch with a message naming the finding (for example
+ `gate: guard nil consult in export (round 1)`), `--no-gpg-sign`. Push with
+ `git push origin HEAD:<headRefName>` only after the round's fixes are complete;
+ with `--dry-run`, commit nothing and show the diff instead.
+
## Step 4: Re-run and loop
1. Re-run Codex on the amended diff (same scope; fixes are in the working tree, so
the default and path scopes pick them up automatically).
2. Append all standing rebuttals to the contract under a `Prior rebuttals:` section
so Codex can either accept them or refute them with new evidence.
3. A round = one Codex run plus full disposition of its findings.
4. Loop until Codex returns `NO FINDINGS`, for a maximum of 2 full rounds
(initial review + one re-review). If findings remain after round 2, STOP.
Do not fix further, do not start round 3; surface the residue to User verbatim
with your assessment of each remaining item.
+ In **review-only** PR mode there is nothing to re-run inside one invocation: one
+ invocation is one round. Write the round to the per-PR ledger (Step 5), post the
+ review, and stop. When User invokes the gate on the same PR again after the author
+ pushes, that is round 2: fetch the new head, load the ledger's rebuttals into the
+ `Prior rebuttals:` section, and a finding the author fixed simply does not come back.
+ After round 2 the residue goes to User, not to the PR.
+
+ In **loop** PR mode the two rounds happen inside one invocation exactly like a local
+ gate, with a push between them.
+
## Step 5: Emit the gate ledger
- Write `codex-gate-report.md` to the working directory (markdownlint-clean, no em
- dashes). Never stage or commit it. Contents:
+ Local scopes: write `codex-gate-report.md` to the working directory (markdownlint-clean,
+ no em dashes). Never stage or commit it.
+ PR scope: write or append the same content to
+ `~/.local/state/codex-gate/<owner>-<repo>/pr-<N>.md`, outside every repository,
+ one `## Round N` section per round, with the standing rebuttals listed verbatim
+ under `## Standing rebuttals` so the next invocation can load them.
+
```markdown
# Codex Gate Report
- Date / repo / branch / scope / base
- Codex model + reasoning effort (from the run)
- Rounds executed: N of 2
## Round 1 findings
| # | Severity | Location | Finding | Disposition | Detail |
- (Disposition is FIXED or REBUTTED; Detail is the fix summary or the rebuttal text)
+ (Disposition is FIXED, CONFIRMED, or REBUTTED; Detail is the fix summary, the
+ verification evidence, or the rebuttal text)
## Round 2 findings
(same table, or "NO FINDINGS")
## Verdict
One of:
- PASS - Codex returned NO FINDINGS
- PASS WITH REBUTTALS - clean except findings rebutted with justification
- FAIL - Critical/High/Medium findings remain after 2 rounds (list them)
```
- Finish by telling User the verdict, the one-line summary of each fix, and that the
- Codex session can be resumed with `codex exec resume --last`.
+ Then, in PR mode, post to the PR:
+ - **review-only**: one review, `gh pr review "$N" --comment --body-file review.md`.
+ The body lists only CONFIRMED findings, one bullet each: `file:line`, the claim,
+ and the evidence in your words (never the reviewer's raw output, never a rebutted
+ item, never the word "Codex" as an authority; the gate is the reviewer). End with
+ `Codex gate, round R of 2.` If nothing was confirmed, post nothing and tell User.
+ Never use `--request-changes` or `--approve`; the gate advises, a person merges.
+ - **loop**: after the final round, one comment, `gh pr comment "$N" --body-file`,
+ with the verdict, the one-line summary of each fix, and the round count.
+ - With `--dry-run`, print the body that would be posted and post nothing.
+
+ Finally remove the worktree: `git worktree remove --force "/tmp/gate/pr$N"`.
+
+ Finish by telling User the verdict, the one-line summary of each fix or confirmed
+ finding, the ledger path, and that the Codex session can be resumed with
+ `codex exec resume --last`.
+
## Hard rules
- - Never let a finding disappear without a FIXED or REBUTTED entry in the ledger.
+ - Never let a finding disappear without a FIXED, CONFIRMED, or REBUTTED entry in
+ the ledger.
- Never exceed 2 rounds; residue goes to the user, not into round 3.
- Codex reviews read-only; Claude applies fixes. Do not give Codex write access
(`--full-auto`, `--yolo`, `workspace-write`) during a gate run.
- - No commits, no pushes, no staging as part of the gate.
+ - Local scopes: no commits, no pushes, no staging as part of the gate.
+ - PR scope: never change or push a branch whose author is not User; never post a
+ rebutted finding; never post on the first run of a new repo without `--dry-run`
+ having been shown to User once.