codex-arm

Run a Codex pass over a finished artifact so a different vendor reads the diff before it merges.

The Cross-Vendor Arm A sequence diagram generated by Archify. commit range <base>..HEAD adversarial-review --wait --base <ref> Focus text carries the owner intent blocking / non-blocking Only the output file is trustworthy, never the exit code arm(ticket): receipt Findings folded by class first; empty commit, parent is the tree the gate read branch, SHA, markers Fire the arm Verdict Receipt + handback Builder · own worktree · Sequence participant Builder own worktree Codex · adversarial-review · Sequence participant Codex adversarial-review Ticket Branch · HEAD · Sequence participant Ticket Branch HEAD Thomas · resident router · Sequence participant Thomas resident router Legend request return async trace default message

Scroll sideways for the whole diagram

Open the full diagram ↗opens in a new tab

What it does

codex-arm is the invocation mechanics for the cross-vendor arm: the Codex review that Thomas, or at ticket scope the Builder itself, runs over a completed artifact before it can merge. It covers the command (codex-companion.mjs, the Codex plugin runtime), the argv and quoting traps, and where the verdict gets recorded. It does not decide when to run. That cadence, and the two-pass cap, belong to thomas.md and builder.md. This skill owns the how, not the when.

The reason it exists: a same-vendor reviewer and a cross-vendor one catch different defect classes, because the author reads the ticket and the arm reads the repository. This has been measured directly.

The first per-ticket arm run caught a destructive reset authorizing outside its write transaction, something the author’s own mutation-testing pass had missed. The very next pass, over the fix for that finding, caught a real deadlock cycle the fix itself had introduced. Skipping it would have shipped a production 500 on the only unstick path the product had.

When Thomas reaches for it

What is in front of you Reach for
A ticket’s diff is committed, or the Builder is running its own ticket arm codex-arm
A spec is finished, about to be cut into tickets codex-arm at arm: spec, run by Thomas from the base checkout
A slice is closing codex-arm at arm: slice, same as above
Rin’s own gate needs to run Not this skill. Rin never runs the arm from inside its own review

Prerequisites

  • The artifact under review is already committed. The arm reads a tree, not a working directory.
  • At ticket scope, the Builder runs it from its own worktree, where HEAD already resolves to the reviewed commits. At spec or slice scope, Thomas resolves the head from a detached checkout at that SHA, because --base alone does not say which head the companion compares against.
  • Focus text avoids every shell-special and glob-special character, not a fixed list. It is passed unquoted, word by word, so a naturally written phrase like option (a) never reaches the process; zsh’s glob expansion kills the command at parse time.
  • codex exec review cannot carry both a range and a focus. Measured on codex-cli 0.155.1: codex exec review --base main "<focus>" refuses with the argument --base <BRANCH> cannot be used with [PROMPT] (exit 2); --commit <sha> refuses the same way, and -, the stdin marker, IS that positional, so it is refused too. The repair that suggests itself is worse: piping the focus without - is accepted and silently discarded — the run reviews the range under a generic prompt and exits 0 with a full review, proven by piping a marker string and getting an ordinary review back with zero occurrences of it. So on a non-Claude root the arm uses plain codex exec, with both the range and the intent written as ordinary prompt text and -o/--output-last-message capturing the final message to its own file. On a Claude root the plugin path is primary for a concrete reason, not a preference: adversarial-review takes the focus as an ordinary positional beside --base, which is exactly what codex exec review refuses.
  • A hang is a job now, not a verdict (plugin ≥ 1.0.6). --background returns a job id; status --all --json lists what’s running; result <job-id> --json returns the finished review’s stored final output as its own artifact; cancel <job-id> --json cancels a hang instead of leaving it to rot. Findings have a declared shape in the plugin’s schemas/review-output.schema.json — verdict, summary, findings[] of severity / title / body / file / line_start / line_end / confidence / recommendation — so they fold by field rather than by parsing prose.

What it leaves behind

What happened Where it lands
The verdict The merge decision trail: date, verdict, per-finding resolution, the vendor that ran
Whether tests ran A Tests: line: RAN or NOT RUN — <reason>
The arm range First line of output: commit and file count, so a 0-commit range cannot pass as clean
Codex unavailable cross-vendor arm: NOT RUN — <reason>, which only the owner may accept
A gate worktree’s resources Released via release-worktree-resources.sh before removal, never after

Known failures

Pulled from harness/.agents/memory/recurring-failure-modes.md. All entries below are marked promoted: fixed and already in the contract this page describes.

  • A base mismatched with the checkout compares clean against itself. The companion resolves HEAD from the checkout it runs in. Where that disagrees with --base, the run compares the base branch to itself and returns clean. Caught twice by the operator, not by the gate. Fixed: print the commit and file range as the first output line, stop on 0 commits.
  • Exits 0 on configuration failure. The companion exits 0 on configuration failure and caches state keyed to the workspace root. Fixed: never branch on exit code, only on the output file’s content; never reuse a gate worktree path.
  • Orphaned brokers piled up across reviews. Every codex-companion.mjs call spawned a broker process that outlived the review, 92 orphans (~405 MB) measured across two projects. Fixed: kill the broker by real cwd before removing the gate worktree.
  • A project-level teardown killed shared infrastructure. A project-level teardown target used as the release step stopped the shared test-database container every live Builder was standing on. Fixed: scope release to this worktree alone, or release nothing.
  • A read-only reviewer still moved another agent’s HEAD. A read-only reviewer with shell access still moved another agent’s HEAD via git switch. Fixed: the reviewer is a full agent, so even the ticket-scope arm gets its own detached worktree.

It’s working if

  • The first line of every arm run states the commit and file count of the range under review.
  • A Tests: line is present and reads RAN or names why not.
  • The recorded vendor in the merge trail matches the vendor that actually executed, and a same-vendor lens is never counted as the cross-vendor pass.
  • No gate worktree survives past its review. Resources are released before removal, on every path.
  • A blocking finding from pass 1 gets a pass 2 over the fix, not a judgement call that it can be skipped.

Where it fits

A Builder’s closed loop runs implement → review → simplify → codex-arm (ticket scope) → arm(ticket): receipt → handback, and /skills/dispatch-ticket’s cleanup checks that receipt before removing the worktree. At spec and slice scope, Thomas runs it from the base checkout before releasing a paused shaper or closing a slice. It does not substitute for /skills/review-with-rin, which is the gate itself rather than the pass feeding it.

Back to the skill catalog →