TL;DR: Bringing in a second AI to review your engineering work is mostly an exercise in preserving its independence. Frame your question with your conclusion already inside it and you’ll get an expensive nod in return — nothing close to an independent review. Making it useful means setting rules around how you ask, how you receive, and how you compare. The most counterintuitive lesson: the more accurately it reviews, the less you should simply take its word for everything.
I had Claude write a skill that lets codex help review code. Once the tool was finished, I turned it on itself. Across two rounds, codex caught quite a few bugs and proved me wrong on the spot. A second opinion earns its keep most when it punctures your confidence in your own judgment.
Why Bring In a Second AI at All
Codex is a separate model I run locally (OpenAI’s terminal coding agent, running gpt-5.5), dedicated to reviewing what Claude produces: code, data, and design. Claude is the model that does the work and writes the code; codex is a reviewer from a different lineage. It runs in a read-only sandbox — it cannot modify my files and cannot push any changes. Its role is narrow: it is a probe. You use it to examine something you’ve produced, to see what it can find independently. What you miss yourself usually takes a different source to bring to light.
Another practical advantage: codex sits inside my working window. I don’t have to shuttle content back and forth. Whether I’m calling codex directly from the terminal inside Claude Code, or working through Cowork — a desktop window that bridges to the same local codex instance on my Mac — the second opinion is available inside the same workspace. This is a genuine relief compared to the previous workflow, which required copying code out, pasting it into a separate tool, and carrying the findings back. Both windows connect to the same local codex instance with the same configuration, so the standard is consistent regardless of where I ask.
Why does this matter? Because a model reviewing its own output tends not to see its own blind spots — and tends to nod along with its own prior judgments. A model from a different training regime and a different architecture can reach what the first one missed. This echoes a principle Anthropic emphasized in a workshop on long-running AI agents: models cannot reliably evaluate themselves, so it is better to give one a dedicated adversary than to let it grade its own work. This piece sits under my writing on human-AI collaboration, and it concerns exactly how to wire that adversary in usefully.
Asking a different model is the easy part. The hard part is how you ask, how you receive, and how you compare.
The Biggest Risk with Second Opinions: They Agree with You
If your question already contains your conclusion, a second model will very likely follow you there and give you expensive reassurance. “I’ve already checked this section — it looks fine, can you take another look?” usually yields little more than a costly “yes, looks fine to me.” That is not independent review.
So a second opinion is only as valuable as its independence, and independence is something you design — it does not appear on its own. I use a four-step protocol to hold it in place, each step targeting a specific failure mode. The broader question of how multiple models can calibrate one another is something I explore in more depth in my piece on multi-model cognitive collaboration; here I’m focused on what it takes to preserve independence.
How to Keep a Second Opinion Independent: A Four-Step Protocol
Step one: The question must not carry my conclusion. Prompts stay neutral — they specify what is being reviewed and what the acceptance criteria are, and nothing more. No “I think this part is fine.” My wrapper even surfaces a warning when it detects an embedded conclusion in the prompt. This step prevents what courtrooms call leading the witness.
Step two: The raw output must be filed as a record. Reading and discarding is not enough. The output needs to be retrievable for later comparison and open to scrutiny by others. Evidence that disappears after reading is no evidence at all.
Step three: Codex describes problems only — it does not modify code. This is the safety boundary. The moment a reviewer can directly edit, you lose the ability to distinguish the original author’s intent from the reviewer’s unauthorized changes. Its job is to name the problem and assign a severity level. Whether to fix it, and how, is decided on the other side of that line.
Step four: Cross-comparison is done by me, organized into two columns — consistent findings and divergent findings — and nothing is transcribed wholesale. Consistent findings are adopted; divergent findings go back to the code, and I make a call after reading it myself, noting why I sided with one assessment over the other.
Together, these four steps keep the second opinion independent, evidence-producing, bounded in scope, and free from being accepted without judgment. Each rule looks like an overcorrection in isolation. Drop any one of them and the adversary slowly becomes a mirror.
The More Accurately It Reviews, the Less You Should Simply Accept
This is the most counterintuitive aspect of the whole arrangement, and it was the lesson from running the tool on itself.
Codex found seven problems in the first round. After I addressed them, I asked it to review independently again and it found three more — ten in total. Among them were genuine high-risk vulnerabilities. It found, for instance, that my timeout wrapper would misreport a failure as a success when the subprocess was forcibly terminated by the system. Those findings I adopted, because cross-comparison showed its judgment aligned with my own reading of the code.
But even with that level of accuracy, not everything was accepted. In the second round, one finding noted that my “half-success” check only blocked completely empty output, not output that contained text but lacked substantive review content. The problem was that “substantive semantic content” is not something a machine can verify reliably. I fixed the parts that could be checked mechanically and explicitly left the parts requiring human reading to human judgment.
What gets adopted and what gets held back belongs to whoever holds the full context. Codex is an accurate probe, but it makes mistakes, and so do I. When two fallible models serve as each other’s check, a problem that both flag is almost certainly real. A problem only one of them raises requires me to read the code before I decide. Treating either one as an infallible oracle is the move that breaks the whole arrangement.
When the Probe Breaks, Let It Say So Loudly
Probes fail. The local codex installation might be missing, it might crash, or it might run without responding. The protocol’s stance on this is: fail fast, do not retry.
If codex is absent, times out, or exits without producing substantive output, the system degrades immediately: it marks the result honestly as a single-opinion pass, notifies me, and stops. It does not quietly retry five times before reporting a problem, and it does not treat “ran but produced nothing” as success. A dependency that can fail has one critical capability: it must say loudly when it has failed. Silently holding you in an empty wait is the real hazard.
The definition of completion follows the same principle. Whether a review counts as done depends on whether the final message contains substantive content — not on whether the program exited cleanly. An exit code of zero from codex does not mean it produced anything useful. This is the same lesson as the false invariant at the start: treating “finished running” as “finished the job” is exactly where silent failures hide.
Scaffolding Exists to Fill Gaps — Once the Gap Is Gone, Take It Down
These rules are not permanent doctrine. They are scaffolding, built to fill gaps that exist in models as they are today.
Today’s models echo back agreement, report half-successes as successes, and misreport forced terminations as clean exits — so I need these rules to catch those failures. If a future model is independent enough, honest enough, and forthright enough about its own errors, the corresponding rules should be removed, not kept as ritual. A rule’s lifespan should be tied to the problem it was written to solve. When the problem is gone, the rule should leave with it. Every time I revise this protocol, alongside “what should I add,” I ask “what can I now take away.”
A Mirror That Pushes Back
The most valuable moment a second opinion can offer is when it tells you that your confidence in your own judgment was wrong.
Being able to hear that and being willing to accept everything it says are two different capabilities. The first requires letting go of overconfidence in yourself; the second requires letting go of overconfidence in the model. What is genuinely hard is doing both at once.
I brought codex in not because I wanted something that would nod along. I wanted an adversary from a different lineage — something that would push back precisely when I was once again too certain about my own conclusions. And then, the final call belongs to the one who holds the full context: me.
Try it yourself
If you use Claude Code too, this whole setup drops straight into your own project — no need to go through me, and no API key. I deliberately wrote the two files in the appendix below as a generic version: they assume nothing about my project layout and store no personal information. Adapt them to your own workflow and make them your own.
The point isn’t to copy my config, it’s to understand the skeleton of the four independence rules.
There’s one prerequisite: your machine needs the codex CLI installed and logged in, using your own OpenAI account. codex is OpenAI’s terminal tool, a different system from Claude — and it’s exactly that different lineage that makes it good for a second opinion.
In this design, codex runs in a read-only sandbox: it reads, it never edits. The script itself holds no keys and makes no network calls.
It takes three steps:
- Save the appendix’s
SKILL.mdto~/.claude/skills/codex-second-opinion/SKILL.md. - Save the appendix’s
codex-review.shto the same folder. - In Claude Code, say something like: “have codex independently review the token validation in
src/auth.js.” It runs the wrapper, lets codex read the code you pointed it at and surface its findings, then brings the results back to Claude Code. The final call is still yours — you cross-check, read the code, and decide.
Two last reminders, and they’re what this piece is really about.
First, don’t let your question carry your own conclusion. Phrasing like “I think it’s fine, just confirm” makes the model agree with your assumption. What you want is an independent review, not a polite rubber stamp.
Second, the more accurately it reviews, the more you have to go back and read the code yourself. What’s worth stopping for is where it diverges from your own judgment — not the stamp of approval it hands you.
Appendix: the full implementation
Two files, copy-paste and go. The script is short — read it before you run it: it is just those four independence rules baked into the default behavior, so no review relies on memory.
SKILL.md so Claude Code auto-invokes it on "second opinion" (click to copy)
---
name: codex-second-opinion
description: Get an INDEPENDENT second opinion on code, data, or a design by having codex (a different model) review it — without it just agreeing with you. Use when the user asks for a "second opinion", to "have codex review/check this", an "independent review", "cross-check", or to audit security-critical or complex code before trusting it. Trigger words include 第二意見, 獨立審查, 讓 codex 看一下, 交叉驗證, セカンドオピニオン, codex review, double-check this. Requires the codex CLI (logged in) installed locally. Do NOT use for applying fixes — this only surfaces problems; you decide what to do.
allowed-tools: Bash(bash:*)
---
# codex second opinion
Bring in codex (a different model from a different training regime) as an **independent**
reviewer of work — code, data, or design. The whole value is independence: a model reviewing
its own output misses its own blind spots. This skill protects that independence with four rules
so the second opinion does not degrade into agreement.
You run the bundled read-only wrapper `codex-review.sh`. codex runs in a read-only sandbox and
never edits files; it only reports problems. **You** do the final cross-check and decide.
## When to use
- The user asks for a "second opinion", "independent review", "cross-check", or to "have codex look at this".
- Before trusting security-critical code (auth / crypto / payments) or complex logic (race conditions, N+1, deadlocks).
- To sanity-check the credibility of data or an analysis.
## How to run
1. Figure out the target: a path in the project (`--target <path>`) or a snippet/pipeline output (`--stdin`).
2. Write the **review focus** as a neutral assertion or standard to check. Critical: do NOT put your
own conclusion in it. "Review the token check for timing attacks and KV write races" — NOT
"I already checked the auth, just confirm it's fine" (that taints codex's independence; the
wrapper will warn you if you do).
3. Run the wrapper (default reasoning effort is medium; raise to high/xhigh only for security-critical
or genuinely complex targets):
```bash
# review a file
bash ~/.claude/skills/codex-second-opinion/codex-review.sh \
--target src/auth.js "Review token validation, timing attacks, and KV write races"
# review a snippet / pipeline output via stdin
cat data.json | bash ~/.claude/skills/codex-second-opinion/codex-review.sh \
--stdin "Review this data's credibility and field completeness"
# security-critical + a durable audit record under ./codex-audits/
bash ~/.claude/skills/codex-second-opinion/codex-review.sh --effort high \
--target src/payment.ts --audit payment "Review for double-charge and idempotency gaps"
```
4. If the wrapper exits non-zero (codex missing / timed out / blank output), do NOT retry or pretend.
Tell the user this is a single opinion only, and stop.
5. On success, do the cross-check **yourself** (this is the part the wrapper deliberately does not do):
read codex's findings, compare them against your own reading of the code, and split them into
**agreed** and **disagreed**. For anything you disagree on, read the code and decide — do not just
relay what codex said. Treat codex as a probe, not an oracle: a finding both of you reach is almost
certainly real; a finding only one of you reached needs your own judgment.
## Notes
- No API keys involved — codex uses your own OpenAI/ChatGPT login via the local `codex` CLI.
- The only writes are a log under /tmp and, with `--audit`, a record under `./codex-audits/`.
- These rules are scaffolding for today's models. If a model becomes independent and honest enough
on its own, drop the rule it was patching rather than keeping it as ritual.
- This is a template — adapt the trigger words, the reviewer model, the paths, and the rules to
your own workflow. The idea is the four-rule independence protocol, not this exact wording.
codex-review.sh the review wrapper, ~220 lines (click to copy)
#!/usr/bin/env bash
# codex-review.sh — a "second opinion" wrapper that has codex review code/data/design
# written by another model (e.g. Claude), as an INDEPENDENT reviewer.
#
# Why this exists: a model reviewing its own output tends to miss its own blind spots,
# and tends to nod along with its prior judgments. A model from a different training
# regime (codex / gpt-5.5) can catch what the first one missed — but only if you protect
# its independence. This script bakes four rules into the default behavior:
# #1 Independence : the prompt forces codex to evaluate on its own, never assume a prior
# reviewer was right, never agree just to agree (see build_prompt).
# #2 Fail fast : codex missing / times out / exits non-zero / returns blank → it
# degrades to "single opinion", tells you, and STOPS. No silent retries.
# #3 Protocol : (a) the prompt carries none of YOUR conclusions (b) raw output is
# logged (c) codex only describes problems + severity, never edits code
# (d) you do the cross-check yourself — this script only does (a)(b)(c).
# #4 Cost : reasoning effort defaults to "medium"; raise with --effort only when
# a real security/complexity signal warrants it.
#
# SAFETY (read before you run):
# • codex runs in a READ-ONLY sandbox (`-s read-only`): it can read the files you point it
# at, but it cannot modify, delete, or write anything in your project.
# • This script contains NO API keys. codex authenticates with YOUR OWN OpenAI/ChatGPT
# login (whatever your local `codex` CLI is already configured with).
# • The only files this script writes are a log under /tmp and, if you pass --audit, a
# markdown record under ./codex-audits/ in your current directory. Nothing else.
# • No network calls of its own, no eval, no curl|bash. Read the whole thing — it's short.
#
# PREREQUISITES:
# • The `codex` CLI installed and logged in (https://developers.openai.com/codex/cli).
# • bash + perl (perl only needed if you use --timeout; preinstalled on macOS/Linux).
#
# USAGE:
# bash codex-review.sh [options] "<review focus>"
# echo "<content to review>" | bash codex-review.sh --stdin "<review focus>"
#
# OPTIONS:
# --target <path> file/dir for codex to read (repeatable; read in the read-only sandbox)
# --stdin read the content to review from stdin (good for snippets / pipeline data)
# --dir <path> directory codex runs in (default: current directory)
# --effort LEVEL low|medium|high|xhigh (default: medium) [rule #4]
# --audit <slug> also write a durable record to ./codex-audits/<slug>-<date>.md [rule 3b]
# --timeout <sec> max seconds before giving up (default 0 = no limit; uses perl) [rule #2]
# --model <id> override codex model (default: whatever your codex config uses)
# -h | --help
#
# EXIT CODES: 0 ok & non-empty / 2 usage error / 3 codex missing / 4 codex failed or empty
# / 124 timeout. (3/4/124 = the "single opinion" degrade path.)
#
# License: MIT — free to use, modify, and adapt into your own version. No attribution required.
#
# This is a generic, self-contained template. It assumes nothing about your project layout,
# names no tools or services beyond the `codex` CLI, and stores no identity. Make it yours:
# change the rules, the prompt, the paths, the model — the four-rule structure is the idea,
# not the exact wording.
set -uo pipefail
WORKDIR="$(pwd)"
TS="$(date +%Y%m%d-%H%M%S)"
DATE="$(date +%Y-%m-%d)"
EFFORT="medium"
declare -a TARGETS=()
USE_STDIN=0
AUDIT_SLUG=""
TIMEOUT=0
MODEL=""
FOCUS=""
usage() { sed -n '2,57p' "$0"; exit "${1:-0}"; }
# Guards for value-taking options: (1) bail gracefully instead of a set -u unbound crash when
# the value is missing; (2) if the next token looks like another option (-?*), the value was
# probably forgotten — error out instead of silently misparsing.
req() {
[ "$2" -ge 2 ] || { echo "ERROR: $1 needs a value" >&2; exit 2; }
case "$3" in -?*) echo "ERROR: the value for $1 looks like another option ($3) — did you forget the value?" >&2; exit 2 ;; esac
}
while [ $# -gt 0 ]; do
case "$1" in
--target) req "$1" "$#" "${2:-}"; TARGETS+=("$2"); shift 2 ;;
--stdin) USE_STDIN=1; shift ;;
--dir) req "$1" "$#" "${2:-}"; WORKDIR="$2"; shift 2 ;;
--effort) req "$1" "$#" "${2:-}"; EFFORT="$2"; shift 2 ;;
--audit) req "$1" "$#" "${2:-}"; AUDIT_SLUG="$2"; shift 2 ;;
--timeout) req "$1" "$#" "${2:-}"; TIMEOUT="$2"; shift 2 ;;
--model) req "$1" "$#" "${2:-}"; MODEL="$2"; shift 2 ;;
-h|--help) usage 0 ;;
--) shift
if [ $# -gt 1 ]; then echo "ERROR: multiple positional args after -- ; wrap the focus in one quoted string" >&2; exit 2; fi
[ $# -ge 1 ] && FOCUS="$1"; break ;;
-*) echo "Unknown option: $1" >&2; usage 2 ;;
*) if [ -n "$FOCUS" ]; then echo "ERROR: multiple review-focus positional args; wrap the focus in one quoted string" >&2; exit 2; fi
FOCUS="$1"; shift ;;
esac
done
# --- validation ---
case "$EFFORT" in low|medium|high|xhigh) ;; *) echo "ERROR: --effort must be low|medium|high|xhigh (got: $EFFORT)" >&2; exit 2 ;; esac
case "$TIMEOUT" in ''|*[!0-9]*) echo "ERROR: --timeout must be a non-negative integer of seconds (got: $TIMEOUT)" >&2; exit 2 ;; esac
if [ -z "$FOCUS" ]; then echo "ERROR: missing <review focus> (the last positional argument)" >&2; usage 2; fi
if [ "${#TARGETS[@]}" -eq 0 ] && [ "$USE_STDIN" -eq 0 ]; then
echo "ERROR: need at least one --target <path> or --stdin" >&2; usage 2
fi
# rule #2: codex missing = fail fast to the single-opinion path. No retry, no pretending.
if ! command -v codex >/dev/null 2>&1; then
echo "DEGRADE: no 'codex' CLI found on this machine (not installed / broken). Honestly note this is a single opinion and stop. Install: https://developers.openai.com/codex/cli" >&2
exit 3
fi
STDIN_CONTENT=""
if [ "$USE_STDIN" -eq 1 ]; then STDIN_CONTENT="$(cat)"; fi
# rule #3(a) soft guard: warn if the focus carries YOUR conclusion (which would taint codex's
# independence). Warn only, never block. Matches common English/Chinese "already checked" phrasings.
if printf '%s' "$FOCUS" | grep -qiE 'i already|already (checked|verified|confirmed|reviewed)|no (bug|issue|problem)|looks (fine|good|correct)|should be (fine|ok|correct)|我已|已檢查|已確認|沒問題|應該沒'; then
echo "WARNING (rule a): your review focus looks like it carries your own conclusion (e.g. \"already checked / no issues\")." >&2
echo " codex should judge independently — restate it as a neutral \"what to review / what the bar is\", don't feed it your verdict." >&2
fi
# --- build the prompt (rules a + c: independent framing + describe-only, no code edits) ---
build_prompt() {
local stdin_block=""
if [ "$USE_STDIN" -eq 1 ]; then
stdin_block=$'\n## Inlined target content\n```\n'"$STDIN_CONTENT"$'\n```\n'
fi
local target_lines=""
if [ "${#TARGETS[@]}" -gt 0 ]; then
target_lines="## Review targets (read these yourself in the read-only sandbox)"$'\n'
local t
for t in "${TARGETS[@]}"; do target_lines+="- $t"$'\n'; done
fi
cat <<EOF
You are an independent second-opinion reviewer (codex / gpt-5.5). Evaluate the target entirely on your own: do not assume any other reviewer (human or AI) has already checked anything. Your judgment must come only from what you read yourself.
$target_lines$stdin_block
## Review focus (the original request)
$FOCUS
## Output rules (follow strictly)
1. Only describe "problem + severity (high/medium/low)". Do not rewrite code, do not apply fixes, do not output diffs.
2. One finding per item: problem / location (file:line) / severity / why it is a problem / your confidence.
3. Where you judge there is no problem, say "no finding" — do not invent issues to pad the list.
4. Do not rely on or agree with any external conclusion; be faithful only to the facts you read yourself.
EOF
}
PROMPT="$(build_prompt)"
# --- assemble the codex command ---
LOG="/tmp/codex-review-${TS}.log"
LASTMSG="/tmp/codex-review-${TS}.last.md"
declare -a CMD=(codex exec --skip-git-repo-check -s read-only -C "$WORKDIR"
-c "model_reasoning_effort=\"$EFFORT\"" -o "$LASTMSG")
[ -n "$MODEL" ] && CMD+=(-m "$MODEL")
CMD+=("$PROMPT")
# perl alarm wrapper (macOS has no `timeout` binary). Exit codes are faithfully propagated:
# timeout=124, signal-killed=128+sig (so a killed child is NOT misreported as success),
# exec failure=1, else the child's exit code. TIMEOUT=0 → prefix is empty, run directly.
declare -a TPREFIX=()
if [ "$TIMEOUT" -gt 0 ]; then
TPREFIX=(perl -e 'my $t=shift; my $rc; eval { local $SIG{ALRM}=sub{die "TIMEOUT\n"}; alarm $t; $rc=system(@ARGV); alarm 0; }; if($@ eq "TIMEOUT\n"){exit 124} if($rc==-1){exit 1} if($rc & 127){exit(128+($rc & 127))} exit($rc>>8);' "$TIMEOUT")
fi
runit() { if [ "${#TPREFIX[@]}" -gt 0 ]; then "${TPREFIX[@]}" "$@"; else "$@"; fi; }
echo "codex-review: effort=$EFFORT targets=[${TARGETS[*]:-stdin}] dir=$WORKDIR log=$LOG"
# --- run (rule b: raw output goes to the log) ---
runit "${CMD[@]}" 2>&1 | tee "$LOG"
rc="${PIPESTATUS[0]}"
# rule #2: "done" = exit 0 AND a non-empty final message. A half-success must not be silently
# reported as success.
if [ "$rc" -eq 124 ]; then
echo "DEGRADE: codex timed out after ${TIMEOUT}s. No retry. Note it's a single opinion and tell the human. log=$LOG" >&2
exit 124
fi
if [ "$rc" -ne 0 ]; then
echo "DEGRADE: codex exited non-zero (rc=$rc, incl. signal-killed). No retry. Single opinion only. log=$LOG" >&2
exit 4
fi
# The trustworthiness invariant is "the final message (-o) has non-whitespace content", NOT
# "the log is non-empty" (the log always contains the codex banner). Blank/missing = no real
# review happened = treat as failure.
if ! grep -q '[^[:space:]]' "$LASTMSG" 2>/dev/null; then
echo "DEGRADE: codex exited 0 but the final message is blank/missing (half-success, no trustworthy review). Treating as failure. log=$LOG" >&2
exit 4
fi
# --- rule 3(b) durable: --audit writes a record (ensure dir first, verify non-empty before claiming success) ---
if [ -n "$AUDIT_SLUG" ]; then
AUDIT_DIR="$WORKDIR/codex-audits"
AUDIT="$AUDIT_DIR/${AUDIT_SLUG}-${DATE}.md"
mkdir -p "$AUDIT_DIR"
if {
echo "---"
echo "source: codex-review.sh"
echo "date: $DATE"
echo "effort: $EFFORT"
echo "targets: ${TARGETS[*]:-(stdin)}"
echo "raw_log: $LOG"
echo "---"
echo ""
echo "# codex second-opinion raw output — ${AUDIT_SLUG}"
echo ""
echo "**Review focus**: $FOCUS"
echo ""
echo '```'
cat "$LASTMSG"
echo '```'
} >"$AUDIT" && [ -s "$AUDIT" ]; then
echo "audit record: $AUDIT"
else
echo "WARNING: failed to write audit record: $AUDIT" >&2
fi
fi
# --- rule (d) reminder: YOU do the cross-check, splitting "agreed" vs "disagreed" ---
echo ""
echo "OK: codex finished (rc=0). raw log: $LOG"
echo " final message: $LASTMSG"
echo "Next (rule d, you do this): independently cross-check codex's findings against your own"
echo " judgment. Split them into \"agreed\" and \"disagreed\", each with a reason. For"
echo " disagreements, read the code yourself and decide — don't just relay what codex said."
This piece is the method. These three are its skeleton:
- Generate Images Through the Same Codex Bridge: another use of the same Claude × codex wiring, no API key
- You Can Copy the Method, But Not the Feel: why a SKILL.md can be pasted, but judgment can’t
- The Man-Day Is Dead: what “who makes the final call” is actually worth
💬 Comments
Loading...