Recreating Eichler’s Codex pull-request review system
Recreating Eichler's Codex pull-request review system
This guide explains the Codex autoreview system used in the Eichler repository as of August 14, 2026. It is meant for a friend who wants to build the same kind of system in another GitHub repository.
The system is more than a prompt attached to a GitHub Action. It treats model output as untrusted input, validates every claim against the Git diff, limits automatic edits to changed lines, and publishes one stable report on the pull request.
If you want a useful first version quickly, build the read-only review in Phase 1 below. Add automatic remediation only after the review path has good tests and a reliable rate of false positives.
What it does
For each eligible pull request, the system:
- Builds a deterministic manifest of the exact base-to-head change.
- Routes the PR to a content-only shortcut, full Codex review, or manual review.
- Runs separate correctness and code-simplification audits.
- Requires both audits to return JSON that matches committed schemas.
- Validates model findings against the manifest and real changed-line ranges.
- Selects a narrow subset of findings that may be fixed automatically.
- Asks a separate, read-only Codex pass for exact line replacements.
- Validates those replacements without executing code from the PR.
- Pushes one bounded commit with an exact-SHA lease if the branch has not moved.
- Reviews the new commit again after an automatic change.
- Updates one PR comment and one
codex/autoreviewcommit status. - Stores the full machine evidence as GitHub Actions artifacts for 14 days.
It never auto-merges. Its status describes the static review result. It does not claim that tests or builds passed.
The pipeline at a glance
pull_request_target
|
v
deterministic manifest + preflight
|
+--> content-only ------> short terminal report
|
+--> unsafe shape ------> manual-review report
|
+--> full review
|
v
correctness Codex pass
|
v
simplification Codex pass
|
v
trusted validation + selection
|
eligible fixes?
/ \
no yes
| |
| remediation proposal
| |
| trusted evaluation
| |
| one retry for rejects
| |
| validated CAS push
| |
| post-change audits
\ /
|
v
one terminal report + status
The current Eichler workflow can make two to six model calls:
- Two calls for a normal PR with no eligible automatic fix.
- One additional call for the first remediation proposal.
- One bounded retry call if some proposed fixes fail validation.
- Two post-change calls if the system pushes a candidate commit.
Files in the Eichler implementation
The implementation is split into policy, schemas, trusted code, workflow wiring, and tests.
.github/
codex/
README.md
prompts/
correctness.md
complexity.md
remediation.md
schemas/
correctness.schema.json
complexity.schema.json
remediation.schema.json
workflows/
codex-pr-audit.yml
tools/
codex-pr-report/
manifest.mjs
preflight.mjs
report.mjs
remediation.mjs
manifest.test.mjs
preflight.test.mjs
prompt.test.mjs
report.test.mjs
remediation.test.mjs
schema.test.mjs
workflow.test.mjs
Each part has one job:
manifest.mjsproduces the authoritative description of the change.preflight.mjschoosescontent_only,full_review, ormanual_reviewwithout a model call.- The prompt files define the review contracts and trust boundary.
- The JSON Schemas bound the model outputs.
report.mjsvalidates, normalizes, selects, and renders results.remediation.mjsprepares signed edit requests, validates proposals, constructs a candidate Git tree, and records the outcome.- The workflow grants the minimum token permissions needed by each job.
- The tests pin the security and reporting behavior.
The implementation is about 15,000 lines including tests. Copying only the workflow will not reproduce its safety properties.
Fastest way to reproduce the exact implementation
If you can receive the Eichler source files, copy these three paths as one unit:
.github/codex/
.github/workflows/codex-pr-audit.yml
tools/codex-pr-report/
Also copy these two integrations:
{
"scripts": {
"test:codex-pr-report": "node --test tools/codex-pr-report/*.test.mjs"
}
}# In the normal CI workflow
- run: pnpm test:codex-pr-reportRun the tests before changing policy. Then replace each Eichler-specific assumption:
- Rename the comment marker and bot commit identity.
- Replace or remove the meeting-record content-only route.
- Change component classifications and sensitive-path signals in
manifest.mjs. - Review every allowed and denied remediation path in
remediation.mjs. - Replace
oxfmtsetup if the target repository uses another formatter. - Update the sparse checkout if trusted instructions live somewhere
other than root
AGENTS.md. - Select and calibrate the model, effort, Codex CLI version, and API budget.
- Rename the secret if desired, then update every reference consistently.
- Run the entire deterministic suite after each policy change.
Do not transplant codex-pr-audit.yml by itself. It
depends on command names, output files, receipts, schemas, and
validation rules implemented by the other copied files.
Prerequisites
You need:
- A GitHub repository that uses GitHub Actions.
- An OpenAI API project dedicated to PR review.
- A project API key stored as the GitHub Actions secret
CODEX_AUDIT_OPENAI_API_KEY. - A Linux GitHub-hosted runner.
- Node.js for the deterministic scripts.
- A formatter that can check candidate source from trusted
configuration. Eichler uses a pinned
oxfmtversion.
The API project should have its own budget and usage limits. Do not reuse a production application key, a personal all-purpose key, or an organization billing-admin key.
The official Codex GitHub Action documentation is here: https://learn.chatgpt.com/docs/github-action.
The central security rule
The workflow uses pull_request_target because the OpenAI
key must not be exposed to workflow code supplied by the pull
request.
That event is safe only with a strict checkout layout:
workspace root trusted base revision
_audit/pr untrusted pull-request head
The workflow, prompts, schemas, root instructions, and validator
always come from the immutable base SHA. The PR head is checked out into
_audit/pr and treated as evidence. No step executes
scripts, hooks, package-manager commands, tests, builds, migrations, or
binaries from _audit/pr.
Every prompt repeats the same boundary. It tells Codex that all
content under _audit/pr is untrusted, including source
comments, Markdown, AGENTS.md, workflow files, and text
that claims to contain instructions.
The keyed Codex jobs also use:
permissions:
contents: read
# inputs to openai/codex-action
permission-profile: ":read-only"
safety-strategy: drop-sudo
codex-args: '["--ephemeral","--json"]'The Codex action is the final step in each keyed job. The GitHub token is read-only in those jobs. The branch-writing job has no OpenAI key and runs no model.
Eichler also limits the workflow to:
- non-draft PRs;
- PRs that target the default branch;
- same-repository PR branches;
- the exact base and head SHAs from the event.
The workflow re-reads the live PR before publishing or pushing. It drops stale results if the PR moved, closed, changed branches, or returned to draft.
Step 1: Build an authoritative change manifest
Do not let the model decide what changed. Generate a manifest with trusted code before a model runs.
The Eichler manifest records:
- schema version;
- base, merge-base, and head SHAs;
- file status and mode;
- current and previous paths;
- text, binary, symlink, gitlink, and executable shape;
- additions, deletions, and total changed lines;
- exact base-side and head-side hunk ranges;
- component and file classification;
- risk signals;
- a full or targeted coverage plan.
The default coverage limits are 80 files and 8,000 changed lines. Larger diffs get a deterministic targeted plan. The prompts must not claim full coverage when the manifest requests targeted coverage.
Use Git commands that disable user-controlled diff helpers:
git -C _audit/pr diff --no-ext-diff --no-textconv <merge-base> <head>Do not enable copy detection. Treat a copied file as newly added maintenance surface. Record rename-only and mode-only changes explicitly because they may have no honest line anchor.
The manifest is the source of truth for every later SHA, path, hunk, and coverage claim.
Step 2: Add deterministic preflight routing
Run cheap local checks before spending API tokens.
Eichler has three decisions:
content_only: a narrow class of new meeting records. It receives a short report and no model call.full_review: ordinary code, configuration, docs, policy, and mixed changes.manual_review: evidence that cannot safely enter the automated path, such as symlinks, gitlinks, unsupported modes, malformed manifests, deletions, renames, copies, or file-type mismatches.
The content-only rule is specific to Eichler. Replace it with a rule that fits your repository, or remove the shortcut entirely. Default unknown inputs to full or manual review. Do not default them to skipped review.
Preflight output should contain the decision, exact SHAs, stable reason code, and a short plain-text summary. Validate its size and shape before publishing it.
Step 3: Separate correctness from simplification
Eichler uses independent sequential passes so each prompt has one purpose.
The correctness pass looks for:
- functional defects;
- security and privacy failures;
- authorization problems;
- data loss or persistence mistakes;
- broken workflow states and contracts;
- concurrency and idempotency failures;
- accessibility failures;
- missing verification for risky behavior.
The simplification pass looks for:
- duplication;
- unnecessary abstractions or dependencies;
- dead code;
- premature generalization;
- verbose implementations with a materially smaller equivalent;
- maintained generated noise;
- unrelated scope that should be split.
The simplification prompt explicitly protects tests, validation, useful types, domain states, authorization, audit trails, compliance controls, error handling, idempotency, accessibility, and explanatory code. A smaller diff is not automatically a better diff.
Run the two passes sequentially if your API project has a low request-per-minute limit.
Step 4: Require structured output
Pass a committed JSON Schema to each Codex call with
output-schema-file. Do not parse a free-form Markdown
review and hope that headings stay stable.
The correctness schema includes:
- a change summary;
- zero to fifteen findings;
- zero to five human review targets;
- inspected and omitted paths;
- uncertainties.
Each finding includes severity, confidence, category, whether the PR introduced it, the failure mode, evidence, impact, recommendation, verification, and one to four locations.
The simplification schema includes:
- zero to twelve opportunities;
- complexity that should remain;
- scope-split candidates;
- coverage and uncertainties.
Each opportunity includes confidence, benefit, risk, behavior to preserve, verification, locations, estimated net line reduction, independence, and overlap group.
Require additionalProperties: false, item limits, strict
enums, SHA patterns, and bounded location counts throughout the
schemas.
Example action step:
- name: Run correctness audit
id: run-codex
uses: openai/codex-action@<pinned-commit> # pin the reviewed v1 commit
with:
openai-api-key: ${{ secrets.CODEX_AUDIT_OPENAI_API_KEY }}
prompt-file: .github/codex/prompts/correctness.md
output-schema-file: .github/codex/schemas/correctness.schema.json
codex-args: '["--ephemeral","--json"]'
codex-version: <tested-version>
model: <model-you-have-tested-and-budgeted>
effort: <tested-effort>
permission-profile: ":read-only"
safety-strategy: drop-sudoPin both the action commit and Codex CLI version. Choose the model and reasoning effort from your own calibration and budget rather than copying Eichler's current choice.
Step 5: Validate every model claim
JSON Schema validation is necessary but insufficient. Trusted code should reject a result when:
- its base or head SHA differs from the manifest;
- it cites a path outside the manifest;
- its line range is invalid for the cited revision;
- it claims a PR-introduced problem without an anchor inside a real changed hunk;
- inspected and omitted paths do not partition the changed paths;
- it claims full coverage after a targeted or partial review;
- it fabricates line anchors for binary files or gitlinks;
- it exceeds report, finding, location, or string limits.
This rule prevents the common failure where a reviewer finds real but unrelated debt in a file touched by the PR. A reportable PR finding needs at least one location fully contained inside an authoritative changed-line range.
Use base-side ranges for pure deletions. Use head-side ranges for additions and edits. Route mode-only and rename-only concerns to human review when no content hunk can support a finding.
Assign IDs only after validation, for example
INITIAL/C-001 and INITIAL/S-001. If you run a
post-change audit, use CURRENT/ IDs. Do not correlate
ordinal IDs across passes.
Step 6: Render one stable PR report
The workflow maintains one comment identified by a hidden marker:
<!-- your-project-codex-pr-audit -->On each run, update that comment instead of adding another. Publish the comment only after the lifecycle reaches a terminal result.
Eichler's report contains six sections:
- TL;DR.
- Open problems.
- Automatic changes.
- Human decisions.
- Coding-agent follow-up tasks.
- Collapsed coverage and limitations.
The renderer maps validated evidence to one posture:
low_apparent_risk: success; no issues found by the static review.focused_review: success; human attention requested.do_not_merge: failure; a confirmed current problem remains.audit_incomplete: error; the audit did not complete safely.
Publish the same result as a commit status with context
codex/autoreview. State in the description that tests and
builds did not run. The report is advisory and does not approve the
PR.
Before publishing, verify that:
- the report starts with the trusted marker;
- the evidence SHAs match the live PR;
- the report stays inside GitHub's comment size limit;
- the posture agrees with the validation-complete flag;
- the PR is still open and points to the audited head.
Keep the normalized JSON and complete locations in an artifact even if the public comment uses a compact representation.
Step 7: Add automatic remediation only after review is stable
Automatic remediation is a second system layered on top of review. Keep the first version read-only if you do not have time to implement these checks.
Eichler selects only:
- high-confidence correctness findings introduced by the PR; and
- high-confidence, low-risk, independent simplifications with no overlap.
Trusted code turns each eligible source into a remediation request. The request contains:
- the exact base and head SHAs;
- a stable source ID;
- allowed file paths;
- exact writable head ranges;
- exact insertion points paired to cited base-side deletions;
- edit and line limits;
- a SHA-256 receipt over the canonical request bytes.
The remediation model does not return a patch or shell commands. It returns exact edit operations:
{
"path": "src/example.ts",
"start_line": 42,
"delete_line_count": 2,
"expected_lines": ["old line one", "old line two"],
"replacement_lines": ["new line"]
}The validator checks each source independently. One malformed fix does not discard an unrelated valid fix. A multi-file fix remains atomic.
Reject an edit if:
- the receipt, source ID, or SHA is wrong;
- the path is not authorized for that source;
- the original lines differ by one byte;
- the edit is outside the source's cited changed hunk;
- edits overlap;
- the change touches a denied file type or directory;
- the result exceeds the edit budget;
- a simplification misses its promised net-line range;
- the candidate is not already formatter-clean;
- the candidate introduces a missing symbol or import;
- the edit requires a product, legal, or external decision.
Eichler permits one fresh proposal for rejected sources only. The retry request contains only those sources. Accepted first-pass edits are locked and are never reconsidered.
Step 8: Keep model access separate from write access
The job that asks Codex for edits has:
- the OpenAI key;
- read-only repository access;
- no write token;
- no ability to push.
The later job that constructs and pushes a commit has:
- no OpenAI key;
- no model call;
- trusted validator code from the base revision;
- write access only for the bounded final operation.
In Eichler v1, automatic writes are limited to formatter-clean JavaScript and TypeScript. The validator denies workflows, migrations, generated output, snapshots, SQL, lockfiles, binaries, symlinks, gitlinks, file creation, deletion, rename, and mode changes.
Construct the candidate with Git plumbing or another method that does
not invoke hooks, filters, or code from the PR. Run
git diff --check. Feed candidate bytes to the trusted
pinned formatter in check mode. Do not let the formatter discover
configuration under the PR checkout.
Immediately before the push, fetch the live PR again. Push with an exact lease:
git -C _apply/pr push --porcelain \
--force-with-lease="refs/heads/$HEAD_REF:$EXPECTED_HEAD_SHA" \
origin "$FIXED_SHA:refs/heads/$HEAD_REF"The lease turns the write into a compare-and-swap operation. If a person or bot updated the branch, the push fails instead of overwriting the new work.
Use a clear bot author such as:
Your Project Codex Remediation <codex-remediation@users.noreply.github.com>
Step 9: Review the automatic commit again
A push made with GITHUB_TOKEN does not start another
copy of the same workflow. This is useful because it prevents
remediation loops, but it also means normal CI may not run for the bot
commit.
After a successful push, mark codex/autoreview pending
on the new SHA and run fresh correctness and simplification audits
inside the same workflow. Render the terminal report from the
post-change evidence.
Tell the reviewer that normal tests and builds still need to run on the final head. Never describe the automatic edit as a verified fix unless a separate trusted process actually ran the required verification.
Step 10: Preserve evidence and fail closed
Upload immutable artifacts named with the PR number and audited SHA. Eichler stores:
- the manifest;
- raw and normalized audit JSON;
- validation results;
- remediation requests and SHA-256 receipts;
- first-pass and retry evaluations;
- the final patch and candidate state;
- the terminal Markdown report.
Use a short retention period that fits your audit needs; Eichler uses 14 days.
Add explicit terminal paths for:
- preflight failure;
- incomplete model output;
- invalid or stale evidence;
- proposal-generation failure;
- proposal-validation failure;
- push failure;
- post-change audit failure;
- publication failure after a successful push.
If a candidate was pushed and later reporting fails, mark that exact candidate as unverified and incomplete. Do not reuse prose produced for the previous head.
Suggested build order
Phase 1: useful read-only review
- Add the trusted base/head checkout layout.
- Generate an exact change manifest.
- Write one correctness prompt and schema.
- Run
openai/codex-actionread-only. - Validate SHA, path, location, hunk, and coverage claims.
- Update one marked PR comment.
- Publish
codex/autoreviewon the audited SHA. - Add tests for stale results, prompt injection, invalid anchors, and moved branches.
This phase provides most of the review value with a much smaller attack surface.
Phase 2: review quality
- Split correctness and simplification into separate passes.
- Add full versus targeted coverage.
- Add review targets, protected complexity, and uncertainties.
- Store normalized evidence as artifacts.
- Calibrate findings against a set of known good and bad PRs.
Phase 3: bounded remediation
- Select only high-confidence eligible sources.
- Sign canonical remediation requests.
- Request exact line operations from a read-only model job.
- Validate each source independently.
- Add one retry for rejected sources.
- Build a candidate without hooks or PR-controlled code.
- Push with an exact-SHA lease from a keyless writer job.
- Re-audit the pushed candidate.
Do not start Phase 3 until the first two phases have strong deterministic tests.
Tests worth copying as behavior
Eichler tests the following contracts:
- action and third-party versions stay pinned;
- keyed jobs remain read-only and model steps remain last;
- the writer job has no OpenAI key;
- trusted policy comes from the base revision;
- the PR head remains under the evidence directory;
- fork PRs, drafts, and non-default targets do not enter keyed jobs;
- manifests handle additions, deletions, copies, renames, binary files, modes, symlinks, gitlinks, merge bases, and hunk ranges;
- preflight rejects malformed and structurally unsafe evidence;
- schema limits and prompt contracts stay aligned;
- model findings require real changed-line anchors;
- stale SHAs and invalid coverage claims fail validation;
- remediation cannot escape per-source paths and hunks;
- edits are atomic per source and non-overlapping across sources;
- retry inputs contain only rejected sources;
- candidates are formatter-clean and pass
git diff --check; - branch writes use an exact lease;
- the report remains bounded and keeps all current issues;
- one marked comment is updated instead of multiplied;
- incomplete or failed runs publish a fail-closed status.
Run the Eichler deterministic suite with:
node --test tools/codex-pr-report/*.test.mjsRepository settings and operating notes
Configure these items in the target repository:
- Add the
CODEX_AUDIT_OPENAI_API_KEYActions secret. - Give the workflow only job-level permissions. Start the workflow
with
permissions: {}. - If you enable remediation, allow the writer job to update same-repository PR branches.
- Add
codex/autoreviewto branch protection only after failure behavior has been tested. - Set concurrency to one active run per PR and cancel stale runs.
- Keep prompts, schemas, validators, and workflow changes under normal code review.
- Review OpenAI API usage and GitHub Action logs after rollout.
The current Eichler model and effort settings are cost choices, not part of the design. Measure false positives, missed seeded defects, token use, latency, and cost before choosing your own settings.
Common shortcuts that break the design
- Checking out the PR head over the workspace root in a
pull_request_targetjob. - Running
npm install, tests, builds, or repository scripts from the untrusted PR. - Giving a model job both the API key and a write-capable GitHub token.
- Asking Codex to post comments or push directly.
- Accepting free-form prose as machine evidence.
- Trusting a cited file and line without checking it against a real diff hunk.
- Publishing a result after the live PR head moved.
- Letting remediation touch an entire changed file instead of source-specific hunks.
- Retrying the whole patch and allowing accepted edits to change again.
- Calling a static edit verified when tests did not run.
- Using a normal force push instead of an exact-SHA lease.
- Auto-merging based on the model's report.
Minimum viable workflow skeleton
This skeleton shows the trust boundary. It omits the validators and publisher that you must add before relying on the result.
name: Codex PR review
on:
pull_request_target:
types: [opened, synchronize, reopened, ready_for_review]
concurrency:
group: codex-pr-review-${{ github.event.pull_request.number }}
cancel-in-progress: true
permissions: {}
jobs:
review:
if: >-
github.event.pull_request.draft == false &&
github.event.pull_request.base.ref == github.event.repository.default_branch &&
github.event.pull_request.head.repo.full_name == github.repository
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- name: Check out trusted policy
uses: actions/checkout@<pinned-commit>
with:
ref: ${{ github.event.pull_request.base.sha }}
fetch-depth: 1
persist-credentials: false
sparse-checkout: |
AGENTS.md
.github/codex
tools/codex-pr-report
sparse-checkout-cone-mode: false
- name: Check out PR as untrusted evidence
uses: actions/checkout@<pinned-commit>
with:
ref: ${{ github.event.pull_request.head.sha }}
path: _audit/pr
fetch-depth: 0
persist-credentials: false
submodules: false
- name: Build trusted manifest
env:
BASE_SHA: ${{ github.event.pull_request.base.sha }}
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
run: >-
node tools/codex-pr-report/manifest.mjs
--repo _audit/pr
--base "$BASE_SHA"
--head "$HEAD_SHA"
--output _audit/change-manifest.json
- name: Require dedicated review key
env:
AUDIT_KEY: ${{ secrets.CODEX_AUDIT_OPENAI_API_KEY }}
run: test -n "$AUDIT_KEY"
- name: Run structured read-only review
id: codex
uses: openai/codex-action@<pinned-commit>
with:
openai-api-key: ${{ secrets.CODEX_AUDIT_OPENAI_API_KEY }}
prompt-file: .github/codex/prompts/correctness.md
output-schema-file: .github/codex/schemas/correctness.schema.json
codex-args: '["--ephemeral","--json"]'
codex-version: <tested-version>
model: <tested-model>
effort: <tested-effort>
permission-profile: ":read-only"
safety-strategy: drop-sudoSplit validation and publication into later jobs. Pass data through immutable artifacts or bounded job outputs. The publishing job should receive neither the OpenAI key nor a checkout of executable PR code.
Final checklist
Before enabling the workflow on real pull requests, confirm all of these statements:
- Trusted policy always comes from the event's base SHA.
- The PR head is evidence only and lives in a separate directory.
- No keyed job executes PR-controlled code.
- Every model response is schema-bound and independently validated.
- Findings need a real changed-line anchor.
- Coverage claims match the manifest.
- The publisher rejects stale PR identity.
- The report says when tests and builds did not run.
- Model jobs cannot write to the repository.
- A writer job, if present, has no OpenAI key.
- Automatic edits stay within source-specific paths and hunks.
- The push uses an exact-SHA compare-and-swap lease.
- A pushed candidate receives a fresh audit.
- Every terminal failure produces an honest status.
- The workflow never auto-merges.
That separation of duties is the core of the system: Codex analyzes and proposes; trusted code decides what is valid; a separate bounded job performs the write; humans retain the merge decision.