A gated build loop between Claude Code and Codex CLI. Codex writes the plan, Claude approves it, Codex implements that same plan from the same thread, Claude reviews the diff against it.
> /plugin marketplace add ozzaii/codex-claude-loop> /plugin install codex-claude-loop@codex-claude-loop
Repo: ozzaii/codex-claude-loop
What's inside

A gated build loop between Claude Code and Codex CLI. Codex writes the plan, Claude approves it, Codex implements that same plan from the same thread, Claude reviews the diff against it.
brief → codex plans → CLAUDE APPROVES → codex implements → CLAUDE REVIEWS → release
▲ gate 1 ▲ gate 2
cl_impl cl_release
refuses without it refuses without it
Both gates have a consumer, which is what makes them gates rather than notes: cl_impl
refuses to run unless the plan is approved and the plan file still hashes to what was
approved; cl_release refuses unless the review is approved and the tree still hashes
to what was reviewed.
Bash around codex exec. No MCP server, no daemon, no Python.
In Claude Code (recommended, updates handled for you):
/plugin marketplace add ozzaii/codex-claude-loop
/plugin install codex-claude-loop@codex-claude-loop
Anywhere else (installs the skill into ~/.claude/skills/):
curl -fsSL https://raw.githubusercontent.com/ozzaii/codex-claude-loop/main/install.sh | sh
The installer runs doctor when it finishes, so you learn straight away whether your
Codex can drive the loop. Flags: --dir <path>, --ref <tag>, --uninstall.
You need Codex CLI (npm i -g @openai/codex
then codex login), plus jq and git.
In Claude Code: /codex-claude-loop:wave <slug> <brief.md> walks the whole cycle and
stops at both gates for your judgment. /codex-claude-loop:status shows where every slug
stands. /codex-claude-loop:doctor checks the substrate, and says plainly that it never took a
turn, so a green doctor is not read as proof that quota and model availability are fine.
cl_status exits 3 when an implementation is standing with no review approval covering the
current tree, so a script can tell that work is waiting on a human.
From a shell:
source ~/.claude/skills/codex-claude-loop/lib/codex-claude-loop.sh
cl_doctor
cl_plan auth-rl briefs/auth-rl.md # codex writes the plan, thread stays open
# read the plan. Try to refute it. Then:
cl_record_verdict auth-rl plan approve "contracts and migration order hold"
cl_impl auth-rl # same thread implements it, holds the lock
cl_review_human auth-rl # adversarial review of the diff vs the plan
cl_revise auth-rl "1) bucket resets on deploy, persist it 2) …"
cl_record_verdict auth-rl review approve "blockers cleared, tests green"
cl_release auth-rl # refuses unless both gates hold right now
cl_status
cl_wave <slug> <brief.md> drives the same sequence one step at a time. It returns 3
when it is your turn (read the plan, review the diff) and 0 once both gates hold, so you
re-run it after each judgment instead of watching it block.
Unattended, with nobody to judge: cl_codex_gate <slug> runs a Codex reviewer bound to
verdict.schema.json. It
defaults to revise, and an approve carrying blocking items gets downgraded on disk.
Claude can send a follow-up into the implemented lane without making Codex re-read the repository into a cold thread:
/codex-claude-loop:prompt auth-rl "add the missing retry regression test"
# shell equivalent:
cl_prompt auth-rl "add the missing retry regression test"
cl_prompt uses the stored session id with
codex exec resume,
never --last. It takes the same writer lock as cl_impl and cl_revise. If one of
those is running, the follow-up waits; after that process exits, the lane re-checks its
state and only then resumes the session. It never injects a second Codex process beside a
live writer.
The lane must already have an approved plan and a successful implementation. A prompt is allowed to write, so it clears the review approval before it runs, restores the implementation marker only on a clean Codex exit, keeps a distinct JSONL/Markdown transcript, and prints Codex's final response back to Claude. Re-review afterwards.
The Claude Code plugin registers a
PreToolUse hook for normal
git commit, git push, git merge, and git tag Bash calls. If any successful
implementation is not covered by current plan and review approvals, the command is denied
before it runs and the message names the pending slugs.
A verdict stores both the strict HEAD-plus-tree id and the reviewed content tree. That
means committing the exact reviewed working tree does not manufacture a false "changed
after review": a later, separate git push remains allowed. push and tag check the
published HEAD tree rather than the combined working tree, so committing only half the
reviewed change is still refused. Change one byte after the review and the guard closes
again.
This guard ships only with the plugin install, not the standalone curl | sh skill
install. It binds ordinary Claude Code Bash calls, not a human terminal, Codex's sandbox,
or deliberately wrapped/aliased git commands; it is a workflow rail, not a security
boundary. For an intentional one-session bypass, start Claude Code with
CL_GIT_GUARD=0.
Most Claude + Codex setups paste a plan into a fresh Codex prompt, so Codex implements a
summary of reasoning it never did. Here cl_plan opens a persistent thread, has Codex
author the plan as the implementer, and stores the thread id. cl_impl resumes that
exact thread. The review gate judges the diff against the stored plan file, so "it built
something else, but nice" is a blocking finding rather than a shrug.
Implementation is the only serialized step, so planning the next brief is free to run
during it. Reviewing is not: a reviewer reading a tree that a writer is editing judges
half-written files, so cl_review_human and cl_codex_gate refuse while the writer lock
is held.
impl(N) [======= writes tree, lock held =======]
plan(N+1) [== read-only, overlaps freely ==] approve ✓ → impl(N+1) next
review(N-1) [== after the lock clears ==]
Genuine review/implement overlap needs the lanes in separate worktrees, one
CL_REPO each. That is what CL_WRITABLE_ROOTS exists for. Set
CL_ALLOW_CONCURRENT_REVIEW=1 only if you accept reviewing a moving tree.
A gate that always approves is worse than no gate.
cl_impl refuses unless the plan verdict is approveplan.md afterwards and the
approval stops counting, because the verdict carries the plan's hash. A plan that
cannot be hashed at all (deleted file, no sha256 tool) also voids it, rather than
reading as "no mismatch found"cl_record_verdict refuses to record an approval it could never verify later, and
refuses one that does not describe what you were shown: the plan gate and the review
record what they printed, so a plan or a tree that moved between reading and approving
is refused instead of being approved unreadcl_impl refuses without a stored thread id, and never falls back to resume --lastcl_prompt refuses without a successful implemented lane, resumes its exact stored
session, and sequences behind the same writer lock instead of competing in the worktreecl_release refuses unless the review approved this content: it stores the strict
HEAD-plus-tree id and the full reviewed content tree, so an exact commit survives but a
byte change does not. It refuses outright when any path carries assume-unchanged or
skip-worktree, because a lying index would otherwise hold that digest still while the
file changedcl_revise keeps every round's blocking items, and each later review is handed them
with instructions to judge intent rather than wording: a fix that satisfies the sentence
and misses the point is still blockingcl_plan clears both verdicts, the recorded base, and the success marker;
cl_impl and cl_revise clear the review approval before they touch the treeapprove plus a non-empty blocking[] becomes revise, rewritten in the file. A
blocking that is not an array is refused outright rather than counted as zerocl_impl waits on the lock. A stale lock is reclaimed by renaming it, so two
waiters cannot both reclaim, and only the owning pid may releasecl_lock_recover, which still refuses while anything it can identify is alivecl_revise refuses a blocking item that starts a line with =====, the fence the
harness uses to delimit its sections, because such an item breaks the prompt's structure.
This is not injection defence: findings and instructions are the same kind of text at the
same level, so a heading or plain English does the same job. Keep a human between an
untrusted reviewer and a workspace-write threadThe CLI moves, and a moved flag used to mean a lane that died silently into its log.
cl_doctor probes the four capabilities the loop depends on (exec --json, exec -o,
exec --output-schema, exec resume) by the exact spelling the driver invokes| Env | Default | Notes |
|---|---|---|
CL_REPO | git root of $PWD | the tree Codex writes |
CL_STATE | ~/.codex-claude-loop/<repo>-<hash of its path> | plans, verdicts, thread ids, logs, kept outside the repo. The path hash keeps ~/a/app and ~/b/app apart |
CL_ALLOW_CONCURRENT_REVIEW | 1 lets a review run while a writer holds the lock | |
CL_SANDBOX | workspace-write | implementation sandbox |
CL_PLAN_SANDBOX, CL_REVIEW_SANDBOX | read-only | raise only on hosts where sandboxing itself fails |
CL_IMPL_MODEL, CL_PLAN_MODEL, CL_REVIEW_MODEL | codex default | plan cheap, review strong |
CL_WRITABLE_ROOTS | needed when CL_REPO is a linked worktree | |
CL_NET | 1 grants the sandbox network for test gates | |
CL_LOCK_TIMEOUT | 7200 | seconds to wait for the writer lock |
CL_GIT_GUARD | 1 | 0 disables the Claude Code git publication hook for that session |
Each of these cost hours before it became a line of code.
codex exec resume <id> rejects global flags placed after resume. -C/-s/-o goFAQ
codex-claude-loop is a Claude Code plugin with 5 hand-picked skills for development work, indexed on Flowy. Install it with the command on its page. It includes codex-claude-loop, doctor, prompt. Its skills do not fire on their own yet. Request auto-invocation to have Flowy route them as you prompt. Free and open source.
Is this plugin yours?
Claim it with GitHubSubmit a pluginPromote it