Automatic code review
Deliverable: every time Claude Code finishes a turn with uncommitted changes, a Stop hook runs a Codex review of those changes and prints its verdict.
Prerequisites: Claude Code and Codex CLI both configured with the same MIRAPI_API_KEY (see Claude Code and Codex CLI); a git repository.
1. The review script
Section titled “1. The review script”Create ~/.claude/review.sh:
#!/usr/bin/env bash# Run a Codex review of the uncommitted changes.set -euo pipefail
DIFF_STAT=$(git diff HEAD --stat || true)
# Exit silently when there is nothing to review.if [[ -z "$DIFF_STAT" ]]; then exit 0fi
codex exec "Review the changes below for bugs, security issues, and broken invariants. Be concise.
$DIFF_STAT" 2>/dev/null || trueMake it executable:
chmod +x ~/.claude/review.shThe script captures a one-line summary of the staged and unstaged changes (git diff HEAD --stat), hands it to Codex, and lets Codex print the review to the hook’s output. The trailing || true keeps a failed Codex call from surfacing as a hook error.
2. The Stop hook
Section titled “2. The Stop hook”Add the hook to your project’s .claude/settings.json:
{ "hooks": { "Stop": [ { "hooks": [ { "type": "command", "command": "bash ~/.claude/review.sh", "timeout": 120 } ] } ] }}The Stop event fires whenever Claude Code pauses for your input — exactly the moment a review is useful. The hook fields are:
| Field | Purpose |
|---|---|
type |
"command" runs a shell command |
command |
The command to execute — here bash ~/.claude/review.sh |
timeout |
Maximum seconds the hook may run before being killed (120) |
To keep the hook out of git, use .claude/settings.local.json instead; global hooks live in ~/.claude/settings.json.
3. Verify it fires
Section titled “3. Verify it fires”First test the script on its own: change a tracked file, then run it directly.
bash ~/.claude/review.shYou should see the Codex review in the terminal. Then ask Claude Code to make a change and wait for the hook output — the verdict appears in the hook’s output section once Claude Code pauses.
Two protocols, one key
Section titled “Two protocols, one key”Claude Code and Codex CLI speak different protocols, so they use different base URLs on MirAPI:
| Agent | Protocol | Base URL | Authentication |
|---|---|---|---|
| Claude Code | Anthropic Messages (POST /v1/messages) |
https://api.mirapi.ai (no /v1) |
ANTHROPIC_AUTH_TOKEN=$MIRAPI_API_KEY |
| Codex CLI | OpenAI Responses (POST /v1/responses) |
https://api.mirapi.ai/v1 |
env_key = "MIRAPI_API_KEY" |
Claude Code
Section titled “Claude Code”Point the Anthropic client at the bare origin and pass the key through the auth-token variable:
export ANTHROPIC_BASE_URL="https://api.mirapi.ai"export ANTHROPIC_AUTH_TOKEN="$MIRAPI_API_KEY"export ANTHROPIC_MODEL="deepseek-chat"Do not append /v1 — Claude Code builds the /v1/messages path itself. Full setup in Claude Code.
Codex CLI
Section titled “Codex CLI”Register MirAPI as a provider in ~/.codex/config.toml:
model = "deepseek-chat"model_provider = "mirapi"
[model_providers.mirapi]name = "MirAPI"base_url = "https://api.mirapi.ai/v1"env_key = "MIRAPI_API_KEY"wire_api = "responses"Codex appends /responses to the base URL, so it needs the /v1 suffix. Full reference in Codex CLI.
Because both agents use the same key, they draw from the same prepaid balance. Watch the spend:
curl https://api.mirapi.ai/api/log/token -H "Authorization: Bearer $MIRAPI_API_KEY"curl https://api.mirapi.ai/api/usage/token -H "Authorization: Bearer $MIRAPI_API_KEY"/api/log/token lists per-request rows; /api/usage/token returns account-level totals. Text is metered per token (input / output / cache read), reasoning tokens bill as output, and prompt caching is enabled automatically on OpenAI-compatible models. Reconcile against the console billing log (https://console.mirapi.ai).
Error handling
Section titled “Error handling”| Status | Meaning | What to do |
|---|---|---|
| 401 | Key missing, invalid, or unknown | Check the exported key |
| 403 | Key valid but refused — balance, whitelist, quota, or IP | Fix the cause, then retry |
| 413 | Payload too large | Trim the diff sent to the model |
| 429 | Rate limited | Jittered exponential backoff (1s→2s→4s, cap ~30s); no Retry-After header |
| 500 | Gateway or upstream error | Idempotent requests can be retried safely |
The gateway does not retry for you. Every error message ends with a MirAPI request ID — include it in support tickets.
Pitfalls
Section titled “Pitfalls”- Hook stdout is shown to Claude Code — keep the script silent except for the verdict.
- The review runs inside the hook, so a long diff delays the next turn; keep the timeout tight and the prompt concise.
git diff HEAD --statcovers staged and unstaged changes but not untracked files; addgit add -N .(or switch togit status --porcelain) if new files matter.- If Codex is not logged in or misconfigured, the script exits quietly (
|| true) — test it standalone first.
Approximate cost
Section titled “Approximate cost”Each review is one short Codex turn — typically a few thousand tokens, so a fraction of a cent to a few cents depending on the diff size and the model. Exact amounts show up in the console usage log and GET /api/log/token.