-
-
Save anchpop/56688d3526e1d4019db7c4fc1bcb2b2c to your computer and use it in GitHub Desktop.
I prefer Claude over Codex, but I find it helpful to have Codex review Claude's work. This gist helps you accomplish that. Paste it into Claude. It will give Claude instructions for how to set up Codex for code reviews. The code reviews trigger automatically when Claude attempts to commit, so Claude will always see them.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Set up a PreToolUse hook that runs OpenAI Codex to review staged changes before any git commit command. Claude has final authority — if Codex flags issues, the first commit is blocked and the issues are shown to Claude, but Claude can commit again to override. | |
| Prerequisites: `codex` (with `--output-schema` support) and `jq` on your PATH. | |
| 1. Create `~/.claude/hooks/codex-review.sh`: | |
| ```bash | |
| #!/usr/bin/env bash | |
| # Runs Codex to review staged changes before committing. | |
| # First attempt: Codex reviews. If Codex approves, commit proceeds. | |
| # If Codex finds issues, commit is blocked and issues are fed back. | |
| # Second attempt with same diff: commit proceeds (Claude has final authority). | |
| # | |
| # Usage: | |
| # As a hook: piped JSON from Claude Code PreToolUse | |
| # Manual: codex-review.sh --manual [directory] | |
| MANUAL=false | |
| REPO_DIR="" | |
| if [ "${1:-}" = "--manual" ]; then | |
| MANUAL=true | |
| REPO_DIR="${2:-.}" | |
| INPUT="" | |
| else | |
| INPUT=$(cat) | |
| # Only run on `git commit` invocations. The hook fires on every Bash tool | |
| # call, so we filter here on the command from the tool input JSON. This is | |
| # more reliable than the settings.json `if` pattern, which can miss chained | |
| # commands like `git add -u && git commit ...`. | |
| COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // ""') | |
| case "$COMMAND" in | |
| *"git commit"*) ;; | |
| *) exit 0 ;; | |
| esac | |
| # Use the working directory from the hook input so we get the right repo's | |
| # diff even when Claude is working in a different project. | |
| # Commands often start with `cd /some/path && git commit ...`, so extract | |
| # the cd target if present; otherwise fall back to .cwd from the JSON. | |
| CD_DIR=$(echo "$COMMAND" | sed -n 's/^cd \([^ ]*\) *&&.*/\1/p') | |
| if [ -n "$CD_DIR" ] && [ -d "$CD_DIR" ]; then | |
| REPO_DIR="$CD_DIR" | |
| else | |
| REPO_DIR=$(echo "$INPUT" | jq -r '.cwd // "."') | |
| fi | |
| fi | |
| DIFF=$(git -C "$REPO_DIR" diff --cached) | |
| if [ -z "$DIFF" ]; then | |
| DIFF=$(git -C "$REPO_DIR" diff) | |
| fi | |
| if [ -z "$DIFF" ]; then | |
| if [ "$MANUAL" = true ]; then | |
| echo "No changes to review." | |
| fi | |
| exit 0 | |
| fi | |
| # Hash the diff to track what's already been reviewed | |
| DIFF_HASH=$(echo "$DIFF" | sha256sum | cut -d' ' -f1) | |
| REVIEW_CACHE="/tmp/codex-review-${DIFF_HASH}" | |
| # If this exact diff was already reviewed, let Claude commit | |
| if [ -f "$REVIEW_CACHE" ]; then | |
| rm -f "$REVIEW_CACHE" | |
| exit 0 | |
| fi | |
| # Pull recent USER messages from the transcript (not Claude's). The point of | |
| # this hook is to get an independent second opinion, so we deliberately do NOT | |
| # include the assistant's messages — that would just bias Codex toward Claude's | |
| # framing. Real user messages have string-typed content; tool results are arrays | |
| # and assistant messages are type=="assistant". We also drop entries that begin | |
| # with harness-injected <system-reminder> / <task-notification> tags. | |
| USER_CONTEXT="" | |
| TRANSCRIPT="" | |
| if [ "$MANUAL" = false ]; then | |
| TRANSCRIPT=$(echo "$INPUT" | jq -r '.transcript_path // ""') | |
| fi | |
| if [ -n "$TRANSCRIPT" ] && [ -f "$TRANSCRIPT" ]; then | |
| USER_CONTEXT=$(jq -r ' | |
| select(.type=="user" and (.message.content|type)=="string") | |
| | .message.content | |
| | select(test("^<(system-reminder|task-notification)") | not) | |
| ' "$TRANSCRIPT" 2>/dev/null | tail -n 80) | |
| fi | |
| USER_CONTEXT_BLOCK="" | |
| if [ -n "$USER_CONTEXT" ]; then | |
| USER_CONTEXT_BLOCK="The following are recent USER messages from the conversation that produced this diff. They show the user's intent. You are being given ONLY the user side, deliberately — the assistant's (Claude's) messages are withheld so your review is an independent second opinion and not biased by Claude's reasoning. | |
| <user_messages> | |
| $USER_CONTEXT | |
| </user_messages> | |
| " | |
| fi | |
| SCHEMA_FILE=$(mktemp) | |
| cat > "$SCHEMA_FILE" << 'EOF' | |
| { | |
| "type": "object", | |
| "properties": { | |
| "approved": { "type": "boolean" }, | |
| "issues": { "type": "array", "items": { "type": "string" } } | |
| }, | |
| "required": ["approved", "issues"], | |
| "additionalProperties": false | |
| } | |
| EOF | |
| CODEX_STDERR=$(mktemp) | |
| REVIEW=$(cd "$REPO_DIR" && codex exec \ | |
| -c model_reasoning_effort=high \ | |
| --output-schema "$SCHEMA_FILE" \ | |
| "${USER_CONTEXT_BLOCK}Here is the diff to review: | |
| <diff> | |
| $DIFF | |
| </diff> | |
| Review this diff for bugs, logic errors, spaghetti code, and missed edge cases. You are encouraged to explore the surrounding codebase on your own — read other files, grep for callers, follow definitions — whenever it would help you understand whether the change is correct. Don't review the diff in isolation if context elsewhere matters. Set approved to true if the code is ready to commit, false if there are issues worth fixing. Sometimes, the whole approach may just be wrong, and you should say so. That's fine. But, sometimes the diff will be basically correct, and you should say so. That's fine too. One of the most helpful things you can do is identify places where something is overcomplicated; an unnecessary special case when the general case is easy to implement or already there, code duplicated that could be shared, control flow that makes it hard to reason about, types that are insufficiently strong or violate the principle of parse-don't-validate. As well as paying attention to edge cases, as long as those edge cases could actually happen. (Impossible edge cases are not interesting.) One principle is that each function should be correct in isolation - e.g. it should not incorrect behavior for some inputs, even if that incorrect behavior is never exercised given the broader structure of the program. If you must do that, it's helpful to make the input types more narrow (best), add defensive asserts (second-best), or simply write a good comment or clearer function name (third-best). Mantra: make the general case work fast, and work right, then you won't need branches for the special case. Less branching and more general code is the key to maintainable code. Invariants being expressed in types and invalid states being unrepresentable is the key to maintainable code. Code that needs to change in sync being consolidated to one place (DRY), while code blocks that might need to be changed independently being duplicated (WET), is the key to maintainable code." \ | |
| 2>"$CODEX_STDERR") | |
| CODEX_EXIT=$? | |
| rm -f "$SCHEMA_FILE" | |
| # Check if codex actually ran successfully | |
| if [ $CODEX_EXIT -ne 0 ] || [ -z "$REVIEW" ]; then | |
| ERRMSG=$(cat "$CODEX_STDERR") | |
| rm -f "$CODEX_STDERR" | |
| if [ "$MANUAL" = true ]; then | |
| echo "Codex failed (exit $CODEX_EXIT):" | |
| echo "$ERRMSG" | |
| [ -n "$REVIEW" ] && echo "stdout: $REVIEW" | |
| else | |
| echo "Codex review failed (exit $CODEX_EXIT): $ERRMSG" >&2 | |
| fi | |
| # Don't block the commit if codex itself is broken | |
| exit 0 | |
| fi | |
| rm -f "$CODEX_STDERR" | |
| APPROVED=$(echo "$REVIEW" | jq -r '.approved // false') | |
| # If Codex approves, let it through immediately | |
| if [ "$APPROVED" = "true" ]; then | |
| if [ "$MANUAL" = true ]; then | |
| echo "Codex approved the changes." | |
| fi | |
| exit 0 | |
| fi | |
| if [ "$MANUAL" = true ]; then | |
| # Manual mode: just print issues and exit cleanly | |
| echo "Codex flagged these issues:" | |
| echo "$REVIEW" | jq -r '.issues[]' | |
| exit 0 | |
| fi | |
| # Mark this diff as reviewed so the next attempt passes through | |
| touch "$REVIEW_CACHE" | |
| # Feed Codex's issues to Claude via stderr | |
| { | |
| echo "Codex flagged these issues:" | |
| echo "$REVIEW" | jq -r '.issues[]' | |
| echo "" | |
| echo "You have final authority. If you believe the commit should proceed, run the same commit command again. If the issues are valid, fix them first. Codex is a helpful assistant, but it's not perfect. In fact, sometimes it tries so hard to find useful feedback that it gets in the way, writes word vomit, meaningless word salad, and hallucinates issues that aren't real. I'm counting on you to use your judgement. The reason you have final authority is precisely because your judgement is better than Codex's. Be proud of it! And here's the mantra: make the general case work fast, and work right, then you won't need branches for the special case. Less branching and more general code is the key to maintainable code. Invariants being expressed in types and invalid states being unrepresentable is the key to maintainable code. Code that needs to change in sync being consolidated to one place (DRY), while code blocks that might need to be changed independently being duplicated (WET), is the key to maintainable code." | |
| } >&2 | |
| exit 2 | |
| ``` | |
| Make it executable: `chmod +x ~/.claude/hooks/codex-review.sh` | |
| 2. Add the hook to `~/.claude/settings.json` (merge into existing hooks if any are already defined): | |
| ```json | |
| { | |
| "hooks": { | |
| "PreToolUse": [ | |
| { | |
| "matcher": "Bash", | |
| "hooks": [ | |
| { | |
| "type": "command", | |
| "if": "Bash(*git commit*)", | |
| "command": "$HOME/.claude/hooks/codex-review.sh", | |
| "statusMessage": "Codex reviewing changes..." | |
| } | |
| ] | |
| } | |
| ] | |
| } | |
| } | |
| ``` | |
| The `if` field is a fast-path filter; the script also filters internally on the command, so the hook is correct even if `if` is unsupported or its glob misses (e.g. chained `git add -u && git commit ...`). | |
| 3. Verify by running `/hooks` to confirm the hook shows up under PreToolUse. | |
| The review cache is keyed on the diff hash, so if Claude fixes issues and the diff changes, Codex reviews again. If Claude decides the issues aren't worth fixing and commits the same diff again, it goes through. | |
| 4. Create the manual-trigger skill at `~/.claude/skills/codex-review/SKILL.md`: | |
| ```markdown | |
| --- | |
| description: Run Codex to review the current staged or unstaged diff | |
| --- | |
| Run `~/.claude/hooks/codex-review.sh --manual` to get an independent Codex review of the current changes. Share the results with the user. | |
| ``` | |
| This lets you type `/codex-review` in any conversation to trigger a manual review without needing to be mid-commit. |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment