Repository navigation
Releases: ozzaii/codex-claude-loop
Release list
codex-claude-loop v0.6.0 — the lock stops guessing
Codex reviewed v0.5.0 and opened with "not release-ready" again. Its best finding was against the fix shipped in v0.5.0.
A silent log is not a dead writer. Codex goes quiet while it runs tests or blocks on I/O, so reclaiming a lock on log age would admit a second writer beside a live orphan — the exact failure v0.5.0 claimed to close. Age no longer authorizes anything. The resume helper records the pid of the codex child it launches, reclaim requires the bookkeeper and that writer to be gone, and a lock with no recorded writer refuses to clear itself and points at the new cl_lock_recover, which still refuses while anything it can identify is alive.
An approval described the state at record time, not the state that was read. Read a plan, approve it after it changed, and the approval covered text nobody read. Both gates now record what they showed, and cl_record_verdict refuses an approval that does not match.
cl_doctor read a logged-out codex as logged in, because "Not logged in" contains "logged in".
The fence check no longer claims to be injection defence. Findings and instructions are the same kind of text at the same level, so a heading or plain English does what the fence does. The check stays as a structural guard; the claim is gone.
Rejected from the same review: its lead critical said _cl_tree_id always fails because git rejects a zero-byte index. On git 2.50.1, the version it named, that read-tree returns 0. It reasoned from git's source instead of running it.
72 → 75 assertions.
codex-claude-loop v0.5.0 — the lock stops trusting a pid
Four items from a landscape scan, each shipped in the smaller form its own skeptic argued for. Five other proposals were killed on doctrine grounds, including a PreToolUse deny hook whose rule could not match the forgery it was named after and which allowed on timeout.
A dead holder pid does not mean the writer is gone. Kill the shell and its codex exec child keeps writing the tree, so reclaiming the lock on the pid alone let a second writer in beside a live orphan. The lock now records the phase log its holder is about to write, and a dead holder is reclaimed only once that log goes quiet (CL_LOCK_QUIET_MIN, default 5 minutes). While it is still growing the loop waits and then refuses. A lock that recorded no log falls back to pid-only reclaim and says UNVERIFIED.
Findings are data, not instructions. A blocking item comes from a reviewer that read arbitrary repo content and is replayed verbatim into every later judge. One starting a line with =====, the fence this harness uses for its own sections, could close the block early and write instructions into a workspace-write thread. Refused at the door; a fence inside a sentence stays legal.
cl_status exits 3 when an implementation stands with no review approval covering the current tree, and 0 when nothing waits on you.
cl_doctor stops overstating itself: three-state auth (logged in / not logged in / UNKNOWN on a codex without login status), and a closing line saying no turn was taken, so green is not read as proof that quota and models are fine.
62 → 72 assertions.
codex-claude-loop v0.4.0 — let git hash the tree
Second fail-open in two releases, found by the same method: a scout claimed it, and it reproduced on the first try.
_cl_tree_id hashed untracked files by looping over the names git ls-files --others prints. Git quotes any name that needs escaping, so "evil\nname.txt" arrived at git hash-object as a literal quoted string, hash-object could not find it, and that file contributed a constant to the digest no matter what was inside it. A tree tampered with after its review kept a byte-identical id, and cl_release approved it.
The fix deletes the class rather than the instance. A temporary index takes read-tree HEAD + add -A + write-tree, so git hashes every tracked and untracked file by content and returns one sha for all of it. Names bash cannot iterate safely never pass through the shell. The caller's real index is untouched, and there is an assertion for that.
Upgrading: digests changed, so a review approval recorded before this version is refused until you re-review. That is the intended direction.
60 → 62 assertions, including the reproduction itself.
codex-claude-loop v0.3.1 — a lying index could hold the tree digest still
A scout pass over the plugin landscape turned up a hole in this repo, and it reproduced on the first try.
git update-index --assume-unchanged <path> tells the index to stop reporting edits to that path. git status and git diff HEAD then say nothing, so _cl_tree_id returned a byte-identical digest while the file was rewritten, and cl_release would approve a tree edited after its review. Codex runs with workspace-write and can issue that command itself.
Hashing every tracked file to route around the index costs a process per file. So the tree id now refuses when any path carries assume-unchanged or skip-worktree, and names them:
tree: 1 path(s) carry assume-unchanged or skip-worktree, so git will not report edits to them.
tree: clear them before trusting any approval — git ls-files -v | grep '^[a-zS]'
A check that cannot be trusted reports failure, never success.
58 → 60 assertions, including the reproduction. Also in this release: the leak gate now scans tracked files instead of the working directory, after it flagged an untracked working note and I pushed past it.
codex-claude-loop v0.3.0 — carrying intent between rounds
A reader of the launch thread named the failure this loop did not catch:
codex sometimes technically matches the revise feedback but misses the intent, and i don't notice till the next review round. does your hash binding only lock the plan text, or does it somehow prevent that intent drift too?
It did not. Worth stating plainly, because it is the more interesting half of the answer: hashing binds an approval to the state it judged. It says nothing about whether a fix meant what the reviewer meant. Those are different problems, and v0.2.0 only solved the first.
The gap was structural, not a bug. cl_revise handed the blocking items to the Codex thread and kept no copy, so the next review started from the diff alone. It had the evidence and not the question.
v0.3.0
cl_reviseappends each round's items to<slug>.carryover.md- both review paths carry every earlier round into the prompt, and are told to judge each item on intent: name the item, quote the line that satisfies the letter, state the intent it misses
- settled items get marked, so later rounds stop re-litigating them
cl_planclears the carryover, since those items were raised against a plan that no longer exists
53 → 58 assertions. The new ones check the items survive the round, that the next review's prompt contains them, that rounds accumulate rather than overwrite, and that re-planning drops them.
/plugin marketplace add ozzaii/codex-claude-loop
/plugin install codex-claude-loop@codex-claude-loop
codex-claude-loop v0.2.0 — the gates now hold
The 0.1.0 tree was handed to Codex (gpt-5.6-sol, xhigh) as an adversarial reviewer, using this harness's own doctrine. Its opening line:
Not release-ready. Both "hard gates" can report approval for state they did not validate, and the one-writer lock can admit two writers.
Both were true. v0.2.0 closes them.
Gates
- a plan approval survived its own plan file being deleted, its digest being absent, or the host having no sha256 tool: an uncomputable digest read as "no mismatch". Now every branch fails closed.
- gate 2 had no consumer, so "two gates you cannot skip" was a claim the code did not make good on.
cl_releasenow consumes it and refuses unless the review approved this tree, compared by base sha plus a hash of HEAD, uncommitted changes, and untracked file content. - a failed autonomous gate left the previous
approvestanding. It is now cleared before each attempt. blocking: truemade the count fail, and the failure defaulted to zero blockers, which became an approval. The verdict shape is validated independently now.
Lock
- two waiters that spotted the same dead pid could both reclaim. Reclamation is a rename, so exactly one wins; release verifies ownership; INT/TERM exit instead of writing on.
Update resistance
- the
--helpmemo assigned inside a subshell, so it never memoized; probes accepted a flag spelling the driver never invokes. Both fixed.
Plus: state keyed by repo path, the approved plan text travels with the implementation prompt, slug validation, a resumable cl_wave, a transactional installer with a marker-checked uninstall, and 33 → 53 assertions against a stub that now enforces the invocation contract.
/plugin marketplace add ozzaii/codex-claude-loop
/plugin install codex-claude-loop@codex-claude-loop
codex-claude-loop v0.1.0
The gated Claude × Codex build loop, extracted from a private harness that had already driven 45 gated waves across 20 lanes of a production codebase.
/plugin marketplace add ozzaii/codex-claude-loop
/plugin install codex-claude-loop@codex-claude-loop
- Codex authors a plan and implements that same plan from the same persistent thread, not a paraphrase pasted into a cold prompt
- Two judgment gates: Claude approves the plan, Claude reviews the diff against that plan
mkdirwriter lock with stale-holder reclamation, so implementation is the only serialized step and planning N+1 overlaps it- Unattended fallback reviewer constrained by a JSON schema; defaults to
revise, and anapprovecarrying blocking items is downgraded on disk - Zero dependencies beyond
codex,jq,git. 20-assertion offline smoke suite (bash + zsh) against a stubcodex
Field notes on the codex-cli flag ordering, JSON-stream pollution and worktree sandbox traps that cost real hours are in the README.