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
HEADalready resolves to the reviewed commits. At spec or slice scope, Thomas resolves the head from a detached checkout at that SHA, because--basealone 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 reviewcannot 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 plaincodex exec, with both the range and the intent written as ordinary prompt text and-o/--output-last-messagecapturing the final message to its own file. On a Claude root the plugin path is primary for a concrete reason, not a preference:adversarial-reviewtakes the focus as an ordinary positional beside--base, which is exactly whatcodex exec reviewrefuses.- A hang is a job now, not a verdict (plugin ≥ 1.0.6).
--backgroundreturns a job id;status --all --jsonlists what’s running;result <job-id> --jsonreturns the finished review’s stored final output as its own artifact;cancel <job-id> --jsoncancels a hang instead of leaving it to rot. Findings have a declared shape in the plugin’sschemas/review-output.schema.json—verdict,summary,findings[]ofseverity / 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
HEADfrom 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.mjscall 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
HEADviagit 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 readsRANor 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.