# Codex on GitHub: PR Reviews and Actions
Codex can review pull requests and run inside GitHub Actions, so every diff gets an automated read before a human ever opens it. This turns Codex from a thing you invoke by hand into part of your pipelinepipelineAll active sales opportunities across the stages of the sales process, together with their combined potential value and probability of closing.View full definition →: a reviewer that comments on style, catches obvious bugs, flags risky changes, and never gets tired at 11pm on a Friday deploy.
This lesson wires that up concretely. You will see the two ways Codex connects to GitHub, a clean workflow that reviews each PR, the exact tokentokenA token is the basic unit of text that language models process, often a word fragment, whole word, or punctuation mark rather than a single character.View full definition → permissions involved, and how to stop it from quietly draining your budget.
There are two distinct integration paths, and mixing them up causes most of the confusion.
The Codex GitHub app (managed). You install Codex from the ChatGPT side and connect your GitHub account. Codex can then be assigned to issues and pull requests directly in the GitHub UI. You mention it, it reads the PR, and it can propose changes or leave review comments. This is the low-effort path: no YAML, no runner, OpenAI hosts the execution.
Codex CLI inside GitHub Actions (self-driven). You run the Codex CLI as a step in your own workflow. This is what you want when you need control: custom prompts, your own secrets, gating on specific file paths, and output that flows into other steps. You own the runner and the logic.
The managed app is faster to adopt. The Actions route is what a serious team ships, because you can version the review logic in the repo itself and reason about exactly what runs. The rest of this lesson focuses on the Actions route.
A pull request is a diff plus context. When Codex reviews a PR in Actions, the useful pattern is:
1. The workflow triggers on pull_request.
2. A step computes the diff (the changed lines, not the whole repo).
3. Codex reads the diff with a review prompt.
4. Codex posts a comment back on the PR using the GitHub APIAPIApplication Programming Interface: a standardised interface that lets applications communicate and exchange data without knowing each other's internal workings.View full definition →.
The critical design choice is what you feed the model. Do not dump the entire repository into context on every PR. That is slow, expensive, and mostly noise. Feed the diff, plus maybe the files the diff touches. The signal is in what changed.
Here is a minimal, runnable workflow. It runs on every PR, sends the diff to Codex with a focused review prompt, and posts the result as a single comment.
name: Codex PR Review
on:
pull_request:
types: [opened, synchronize]
permissions:
contents: read
pull-requests: write
jobs:
review:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Compute diff
run: |
git diff origin/${{ github.base_ref }}...HEAD > pr.diff
echo "Diff size: $(wc -l < pr.diff) lines"
- name: Review with Codex
env:
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
run: |
npm install -g @openai/codex
codex exec --skip-git-repo-check \
"Review the changes in pr.diff. Flag bugs, security \
issues, and missing tests. Be concise. If it looks \
good, say so in one line." > review.md
- name: Post comment
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: gh pr comment ${{ github.event.pull_request.number }} --body-file review.mdA few things worth noticing.
fetch-depth: 0 pulls full history so the git diff against the base branch actually works. Without it you get a shallow clone and the diff breaks.
codex exec runs Codex non-interactively, which is exactly what you want in CI. It reads the prompt, does the work, and exits. The --skip-git-repo-check flag stops it from complaining about the runner's checkout state.
The final step uses gh, the GitHub CLI, which is preinstalled on GitHub-hosted runners. gh pr comment posts the review. No custom APIAPIApplication Programming Interface: a standardised interface that lets applications communicate and exchange data without knowing each other's internal workings.View full definition → glue needed.
Two different tokenstokensA token is the basic unit of text that language models process, often a word fragment, whole word, or punctuation mark rather than a single character.View full definition → are in play, and treating them the same is a security mistake.
`GITHUB_TOKEN` is the automatic tokentokenA token is the basic unit of text that language models process, often a word fragment, whole word, or punctuation mark rather than a single character.View full definition → GitHub mints for the workflow run. It is scoped by the permissions: block at the top of the file. The block above grants:
contents: read so the checkout can read your code.pull-requests: write so gh pr comment can post.That is the entire footprint. Notice what is missing: no contents: write, no actions: write, nothing that lets the job push code or rewrite the repo. Grant the narrowest set that makes the job work. If your workflow only comments, it never needs write access to code. GitHub's permissions documentation lists every scope.
`OPENAI_API_KEY` is your key for the OpenAI APIAPIApplication Programming Interface: a standardised interface that lets applications communicate and exchange data without knowing each other's internal workings.View full definition →, stored as a repository or organization secret. It authorizes the Codex calls and is what you get billed against. It has nothing to do with GitHub permissions; it is the model-side credential.
Keep them separate in your head: GITHUB_TOKEN controls what the job can do *to your repo*, OPENAI_API_KEY controls what it can do *with OpenAI* (and what it costs).
The dangerous version of this pattern runs on pull_request_target with write permissions and then executes code from the PR. That combination lets a malicious fork run arbitrary commands with your secrets. Do not do it.
The workflow above uses pull_request, not pull_request_target. On the standard pull_request trigger, workflows from forks run with a read-only GITHUB_TOKEN and no access to your secrets, which means the Codex step is skipped for outside contributors. That is the safe default. For internal PRs from branches in your own repo, the secrets are available and the review runs normally.
Three more guardrails:
OPENAI_API_KEY in the environment is how supply-chain attacks happen. Keep review and execution separate.actions/checkout@v4 is fine for a tag, but for anything third-party, pin to a full commit SHA so a compromised tag cannot inject code.Every PR that triggers this workflow makes an APIAPIApplication Programming Interface: a standardised interface that lets applications communicate and exchange data without knowing each other's internal workings.View full definition → call, and large diffs mean large tokentokenA token is the basic unit of text that language models process, often a word fragment, whole word, or punctuation mark rather than a single character.View full definition → counts. A repo with heavy PR traffic can run up real cost if you are careless. Control it at three layers.
Filter what triggers. Only run on the branches and paths that matter. Skip docs-only or generated-file changes:
on:
pull_request:
types: [opened, synchronize]
paths:
- "src/**"
- "!**/*.md"Now a PR that only touches markdown never spends a tokentokenA token is the basic unit of text that language models process, often a word fragment, whole word, or punctuation mark rather than a single character.View full definition →.
Cap the diff you send. A 4,000-line refactor does not need a full-context review comment; the diff itself is the noise. Truncate or reject oversized diffs before the model sees them:
if [ "$(wc -l < pr.diff)" -gt 800 ]; then
echo "Diff too large for automated review; skipping." > review.md
exit 0
fiDebounce on `synchronize`. The synchronize event fires on every push to the PR branch. If someone pushes ten commits in a minute, you get ten reviews. Use concurrency to cancel superseded runs so only the latest push gets reviewed:
concurrency:
group: codex-review-${{ github.event.pull_request.number }}
cancel-in-progress: trueThat single block often cuts spend on active PRs by more than half, because you stop paying for reviews of code that was replaced seconds later.
Knowledge check
1. What is the fundamental shift that happens when Codex reviews PRs inside your pipeline rather than being invoked by hand?
2. Why would a serious team choose the Codex CLI inside GitHub Actions over the managed GitHub app?
3. When Codex reviews a pull request in Actions, why does the recommended pattern compute the diff rather than reading the whole repository?
4. Select ALL correct answers about the managed Codex GitHub app.
Select all the correct answers.
5. Select ALL correct answers describing what an automated Codex PR reviewer is described as doing in the lesson.
Select all the correct answers.
A flat comment is the starting point. Once the plumbing works, the interesting moves are about *where the output goes* and *how structured it is*.
Inline review comments. Instead of one comment on the PR, you can post comments on specific lines using the GitHub review APIAPIApplication Programming Interface: a standardised interface that lets applications communicate and exchange data without knowing each other's internal workings.View full definition →. This requires mapping Codex's findings back to file paths and line numbers, which is where structured outputs earn their keep: have the model return JSON with path, line, and comment fields, then loop over them and post each with gh api. You already met structured outputs in the APIAPIApplication Programming Interface: a standardised interface that lets applications communicate and exchange data without knowing each other's internal workings.View full definition → block; this is a clean place to apply them, because "leave a comment on line 42 of auth.py" is only actionable if the model gives you the line number in a parseable shape.
Gating the merge. A review comment is advisory. If you want teeth, have the job exit non-zero when Codex flags something critical, and mark the check as required in branch protection. Be conservative here: a reviewer that blocks merges on style nitpicks gets disabled within a week. Block only on the categories you genuinely care about, like a leaked secret pattern or a removed test.
Feeding richer context selectively. For a PR touching a single module, pulling in that module's full source (not the whole repo) gives Codex enough context to catch "you changed the function signature but not its callers." The rule holds: expand context deliberately, per the files that changed, never blanket.
If your team is small and you mostly want a competent second opinion on PRs without maintaining YAML, the managed Codex GitHub app is the better call. You assign Codex to a PR and it reviews. You lose the fine control over prompts, gating, and cost filters, but you gain simplicity, and OpenAI handles the runner.
ReachReachThe number of unique people exposed to your message in a given period. Unlike impressions, reach counts each person once, no matter how often they see it.View full definition → for the Actions route when you need any of: custom review criteria versioned in the repo, integration with other CI steps, strict cost controls, or air-gapped handling of which secrets touch which jobs. Most teams start with the app and graduate to Actions when the review logic becomes something they want to own and tune.
OPENAI_API_KEY.contents: read and pull-requests: write is enough to review and comment. Keep it separate in your mind from OPENAI_API_KEY, which controls cost.concurrency block to cancel superseded runs on rapid pushes.