Code Review skill for Codex
Find the 3 a.m. bug before the naming nits. Codex reviews pull requests in priority passes, correctness and security before style, and only reports a defect when it can describe the input that breaks it. Every comment carries a severity label, file and line, and a suggested fix in a respectful tone. It reads PRs through gh, confirms suspicions with throwaway tests, ships area checklists for migrations, auth and async code, and posts nothing until you approve.
curl -fsSL codexskills.dev/install.sh \ | sh -s -- code-review Installs to ~/.agents/skills/code-review/ for every project on this machine.
curl -fsSL codexskills.dev/cli.mjs -o codex-skills.mjsnode codex-skills.mjs install code-review The CLI also searches, updates and removes skills. Run it with no arguments to pick from a list. Needs Node 20.12+.
curl -fsSL codexskills.dev/install.sh \ | sh -s -- --project code-review Installs to .agents/skills/code-review/ in the current repository. Commit it and everyone who runs Codex there gets it.
curl -fsSLO codexskills.dev/downloads/code-review.zipunzip -o code-review.zip -d ~/.agents/skills/ The archive holds the skill folder with its generated agents/openai.yaml. Unzip into .agents/skills/ to scope it to a repo.
& ([scriptblock]::Create((irm codexskills.dev/install.ps1))) code-review PowerShell 5.1 or 7. Installs to %USERPROFILE%\.agents\skills\. Add -Project for the current repo.
104-line POSIX script. No sudo, no telemetry. Read install.sh
Use it in Codex
Start a prompt with $code-review, or pick it from /skills.
You can also just describe the task: Codex reads the description below and loads the playbook when your request matches.
What Codex reads before loading it
Reviews someone else's diff or pull request the way a senior engineer does - intent first, then correctness, security, data safety, concurrency, compatibility and tests before style - with every finding tied to a file and line, a concrete failure scenario, a severity label (blocker, major, minor, nit, question) and a suggested fix, written in a respectful tone. Reads PRs with gh pr diff, confirms suspicions by running code, and posts reviews only after approval. Use when asked to review a PR, a branch, a patch or staged changes, or for a second opinion before merging.
Use it when
- Someone asks for a review of a PR, branch, patch or staged diff
- You want a second opinion before merging a risky change
- Review comments on a team tend to be nits while real bugs slip through
Use something else when
- You want to configure Codex's built-in review command or automatic PR reviews: use codex-code-review
- You are the author responding to review comments: use review-feedback-response
- You need a dedicated security assessment rather than a general review: use security-audit-report
How to check the result
Each blocker or major names a file and line plus a concrete failing scenario, ideally confirmed by a throwaway test, and the verdict matches the highest severity found.
The mistake it prevents
Burying one real defect under twenty style nits, or flagging speculative bugs as blockers without a scenario that triggers them.
A review is useful when the author can act on every comment without a follow-up question, the blocking issues are real (each has a concrete way to fail), and nothing important is buried under nits. Find the bug that would page someone at 3 a.m. before you mention a variable name.
Get the change and its context
gh pr view 123 --json title,body,author,baseRefName,headRefName,additions,deletions,files
gh pr diff 123 # needs network and gh auth
gh pr checks 123 # CI state; do not duplicate what CI already caught
git diff origin/main...HEAD # local branch: three dots = since merge base
git diff --cached # staged changes before a commit
To run or test the change locally, gh pr checkout 123 switches branches; first check
git status and ask before touching a dirty working tree. Prefer git worktree add to
keep the user’s checkout untouched.
Before judging code, establish:
- Intent. What problem does the PR claim to solve? Read the description and linked issue. A correct implementation of the wrong thing is the most expensive bug.
- Conventions. Read
AGENTS.md,CONTRIBUTING.mdand neighbouring code. Review against the repo’s standards, not your preferences. - Blast radius. Which callers, consumers, data and deployments does this touch?
rg -n "<changed function>" --type <lang>to find call sites outside the diff.
Review passes, in priority order
Make separate passes. Each pass asks one question of the whole diff.
| # | Pass | Ask |
|---|---|---|
| 1 | Correctness | Does it do what the intent says for normal, empty, boundary and error inputs? |
| 2 | Security | Untrusted input reaching queries, shells, paths, templates, redirects, deserialisers? Authz on every new entry point? Secrets in code or logs? |
| 3 | Data safety | Migrations reversible and safe under load? Writes idempotent? Anything that can lose or corrupt data? |
| 4 | Failure and concurrency | Timeouts, retries, partial failure, races, shared mutable state, resource cleanup? |
| 5 | Compatibility | Public API, wire format, config, CLI flags, DB schema: does any existing consumer break? |
| 6 | Tests | Would the tests fail if the new logic were wrong? Are the risky paths covered? |
| 7 | Design | Right layer, right abstraction, duplication of existing helpers, complexity the problem does not need? |
| 8 | Readability | Names, comments that explain why, dead code. Only what linters and formatters do not catch. |
For per-area checklists (SQL migrations, auth, async code, React, Go, Python, Java and more), read references/review-checklist.md when the diff touches that area.
Proving a finding
A suspected bug becomes a finding only with a concrete scenario: specific input or sequence, the wrong result, and why. Cheap ways to confirm:
- Trace the call path by reading the callers, not just the changed lines.
- Write a throwaway test in a scratch file or worktree that exercises the case; run it. Do not commit or push it to the author’s branch.
- Check the base branch behaviour too: a problem that predates the PR is not this PR’s blocker; mention it separately.
If you cannot confirm it, phrase it as a question, not a defect.
Severity
| Label | Meaning | Merge? |
|---|---|---|
blocker | Bug, security hole, data loss, or breaks consumers. Concrete scenario given | No |
major | Likely bug in an edge case, missing test for risky logic, design that will hurt soon | Not without discussion |
minor | Real improvement, low risk if deferred | Author’s call |
nit | Taste or polish | Never blocks |
question | You need information to judge | Blocks only until answered |
praise | Something done notably well | - |
Cap nits. More than five nits means you should state the pattern once (“several names
abbreviate configuration; prefer full words”) instead of commenting on each.
Writing comments
- Lead with the label, then the problem, the scenario, and a suggested fix:
**blocker:** `retry()` re-sends the payment request after a timeout, but the
provider may have processed the first one. A slow 200 followed by a retry charges the
customer twice. Pass the existing `idempotency_key` on retries (see `charge()` line 88).
- Talk about the code, not the person: “this loop re-reads the file” rather than “you re-read the file”. Ask real questions; do not phrase demands as questions.
- Explain why. “Use a set here” teaches nothing; “membership checks on this list are O(n) inside an O(n) loop, ~4M operations for the 2k-item case” does.
- Offer code for small fixes with a GitHub suggestion block so the author can apply it:
```suggestion
if not items:
return Decimal("0.00")
```
- Do not comment on formatting a formatter would fix, on code outside the diff (unless the diff makes it newly wrong), or on preferences the repo does not codify.
- Acknowledge good work briefly and specifically when it is real.
Verdict
- Request changes if any blocker exists.
- Comment if majors or questions are open but nothing is proven broken.
- Approve when only minors and nits remain; say they are optional.
Posting the review
Posting is visible to others, so show the review to the user and get a clear yes first.
gh pr review 123 --comment --body-file "$TMPDIR/review.md"
gh pr review 123 --request-changes --body-file "$TMPDIR/review.md"
gh pr review 123 --approve --body "LGTM, nits optional."
Inline comments go through the reviews API in one batch:
gh api repos/{owner}/{repo}/pulls/123/reviews --method POST --input "$TMPDIR/review.json"
{
"commit_id": "<head sha from gh pr view 123 --json headRefOid>",
"event": "COMMENT",
"body": "Summary: one blocker in retry handling, two minors.",
"comments": [
{ "path": "src/payments/retry.py", "line": 42, "side": "RIGHT", "body": "**blocker:** ..." }
]
}
line must fall inside a diff hunk on that side, or the API returns 422. Use
start_line plus line for multi-line ranges.
Output template
When reporting in chat rather than posting:
## Review: <PR title> (#123)
**Verdict:** request changes | comment | approve
**Summary:** <2-3 sentences: what the PR does, overall assessment, the main risk>
### Blockers
1. `src/payments/retry.py:42` - <problem>. Scenario: <input -> wrong result>. Fix: <suggestion>.
### Major
### Minor
### Nits
### Questions
### Not reviewed / out of scope
<generated files skimmed, areas you could not run or verify, pre-existing issues noticed>
Gotchas
- Reviewing only the changed lines misses the caller that now passes
None. Read one level out from every changed function signature. - Deleted code is part of the diff: removed validation, removed tests, removed error handling are frequent sources of regressions.
- Green CI proves only what the tests cover. Check that new tests would fail without the change, not merely that they exist.
- Large generated diffs (lockfiles, snapshots, codegen) hide hand edits. Spot-check that generated files match their generator.
What the zip contains
Codex reads bundled files only when a step points to them. Every one is shown here, so you can review it before installing.
agents/openai.yaml
Gives the Codex app a display name, a one-line description and a starter prompt for this skill.
interface:
display_name: "Code Review"
short_description: "Find the 3 a.m. bug before the naming nits"
default_prompt: "$code-review Review PR 482 and tell me if it is safe to merge" references/review-checklist.md
# Area checklists for code review
Use only the sections the diff touches. Each item is a question that has caught real
production bugs; a "no" is a candidate finding that still needs a concrete scenario.
## Database migrations and queries
- Does the migration lock a large table? Adding a column with a volatile default, adding
`NOT NULL` without a default, changing a column type, or building an index without
`CONCURRENTLY` (Postgres) / `ALGORITHM=INPLACE, LOCK=NONE` (MySQL) can block writes.
- Is the deploy order safe? New code must work with the old schema and old code with the
new schema during a rolling deploy (expand, migrate, contract).
- Is there a down migration, or a stated reason why not?
- Are queries built with parameters, never string formatting?
- N+1: does a loop issue one query per item? Look for ORM lazy loads inside loops.
- Unbounded reads: `SELECT *` without `LIMIT` on a growing table, or loading a whole
table into memory.
- Transactions: is the unit of work atomic? Any external call (HTTP, queue) inside a
transaction that can hold locks for seconds?
## Authentication and authorisation
- Does every new route, resolver, RPC or job check who is calling and whether they may
access this specific object (not just "is logged in")? IDOR is the common miss.
- Are permission checks on the server, not only hidden in the UI?
- Are tokens, passwords and keys kept out of logs, error messages and URLs?
- Are comparisons of secrets constant-time (`hmac.compare_digest`, `crypto.timingSafeEqual`)?
- Session or token lifetime changes: what happens to tokens already issued?
## Input handling
- Shell: any user-influenced string reaching `subprocess(..., shell=True)`, `exec`,
backticks or `os.system`?
- Paths: user input joined into a filesystem path without normalising and checking it
stays inside the intended directory?
- Redirects and URLs: open redirects; server-side fetches of user-supplied URLs (SSRF).
- Deserialisation of untrusted data (`pickle`, `yaml.load` without SafeLoader, Java
native serialisation).
- HTML output: values inserted with `innerHTML`, `dangerouslySetInnerHTML`, `|safe`,
`v-html` without sanitising.
- Size limits on request bodies, uploads, regexes on user input (catastrophic backtracking).
## Error handling and resilience
- Are errors swallowed (`except: pass`, empty `catch`, ignored Go `err`)?
- Do retries have a cap, backoff and jitter, and are retried operations idempotent?
- Does every network call have a timeout?
- Are resources closed on every path (`with`, `defer`, `try/finally`, `using`)?
- Do error messages returned to clients leak stack traces or internals?
## Concurrency
- Shared mutable state accessed from multiple threads, goroutines or async tasks without
a lock or a single owner?
- Check-then-act races: "if not exists then create" without a unique constraint or lock.
- Async: un-awaited promises or coroutines; `forEach` with an async callback; blocking
I/O inside an event loop.
- Go: goroutines without a way to stop (context, done channel); writes to a map from
multiple goroutines; loop variable capture (fixed per-iteration since Go 1.22, but
check the module's `go` directive).
- Caches: invalidation on write, stampede on expiry, unbounded growth.
## API and compatibility
- Removed or renamed fields, endpoints, CLI flags, env vars, config keys, events?
- Changed defaults or semantics of an existing parameter?
- New required fields on requests that old clients do not send?
- Enum additions that old consumers will not handle (exhaustive switches, strict parsers)?
- Versioning and changelog updated if the repo requires them?
## Tests
- Would the new test fail if the implementation were reverted? Look for assertions that
only check "no exception" or that a mock was called.
- Are time, randomness and network controlled, or will the test flake?
- Are the edge cases that the code explicitly handles also tested (empty, null, max)?
- Were existing tests deleted or loosened? Ask why.
## Frontend (React and similar)
- Effects: missing or over-broad dependency arrays; effects that should be event
handlers; missing cleanup for subscriptions and timers.
- Keys: array index as `key` on lists that reorder or filter.
- State derived from props copied into state and then stale.
- Accessibility: interactive elements that are not buttons or links, missing labels,
focus not managed in dialogs.
- User-visible strings not passed through i18n when the app is localised.
## Language traps
### JavaScript / TypeScript
- `==` vs `===`; `||` defaults that swallow `0` or `""` (use `??`).
- `any`, non-null assertions (`!`) and `as` casts hiding real type errors.
- Floating-point money; `parseInt` without a radix on user input.
- Mutating function arguments or shared module-level objects.
### Python
- Mutable default arguments (`def f(x=[])`).
- Naive vs aware `datetime` mixed; `datetime.utcnow()` is deprecated since 3.12.
- Broad `except Exception` around large blocks.
- Iterating and mutating the same dict or list.
### Go
- Ignored errors; errors wrapped without `%w` so `errors.Is` fails.
- `defer` inside loops holding resources until the function returns.
- Nil map writes; nil interface vs typed nil pointer comparisons.
- Context not propagated into downstream calls.
### Java / Kotlin
- `equals` without `hashCode`; mutable objects as map keys.
- Unclosed streams or connections outside try-with-resources / `use`.
- `Optional.get()` without a presence check; platform types from Java in Kotlin.
- Blocking calls on reactive or coroutine dispatchers.
### SQL
- `NULL` semantics in `NOT IN` subqueries; `=` comparisons against NULL.
- Implicit casts that defeat indexes.
- Time zones in timestamp columns.
## Performance (only when the change is on a hot path)
- Work moved inside a loop that used to be outside it.
- New synchronous calls on request paths (network, disk, heavy CPU).
- Payload size changes: new fields serialised on every list item.
- Missing pagination on new list endpoints.
About this skill
Written and maintained by the Codex Skills editors to the same editorial standard as every skill here: a selective description, a real workflow, known pitfalls and a way to verify the work. First published , last updated . MIT licensed. Something wrong or out of date? Tell us.