A heavier, occasional sibling to finalize-with-codex: a deep reuse/dead-code/duplication/naming cleanup pass, run in an isolated git worktree, with a producer stage and an always-run, fresh-context adversarial critique stage before finalize-with-codex's existing commit gate lands the result. Claude stays the architect — the pipeline flags public-API ideas and out-of-scope findings rather than acting on them.
This system does not duplicate finalize-with-codex — it invokes it, unmodified, twice per run (see Relationship to finalize-with-codex below). Install both gists to use this one.
| File | Purpose |
|---|---|
| cleanup-with-codex-SKILL.md | Claude skill — orchestrates the handoff |
| run-cleanup.sh | Shell launcher — worktree lifecycle, the three codex exec calls, rebase/ff-merge |
| cleanup-produce-SKILL.md | Codex skill — the producer: finds and applies the cleanup |
| cleanup-critique-SKILL.md | Codex skill — the critic: fresh-session adversarial review |
| cleanup-produce-schema.json | JSON schema enforcing the producer's output shape |
| cleanup-critique-schema.json | JSON schema enforcing the critic's output shape |
Not included here: response-schema.json and finalize-commit-SKILL.md — this system reuses those from the finalize-with-codex gist as-is.
1. cleanup-with-codex-SKILL.md — Claude skill spec
Installed at ~/.claude/skills/cleanup-with-codex/SKILL.md. Invoked explicitly — this is an occasional, heavier pass, not something to run per-commit:
- Ensure a clean checkout first. If
git status --porcelainshows anything, that's Claude's own uncommitted work: draft a commit message for it and invokefinalize-with-codexto land it before doing anything else, passing an explicitfileslist (covers untracked paths finalize-commit's own diff-based default would miss). CaptureHEADbefore this step aspre_cleanup_head. - Lock check (advisory) —
codex-cleanup.lockin the checkout's git dir; the shell helper's own atomic lock is authoritative. - Write request file —
{"task_summary": "...", "files": [...], "pre_cleanup_head": "<sha>"}to the scratchpad. - Launch
run-cleanup.shviaBashwithrun_in_background=true. Auto-notified on completion. - Report the result — five terminal states:
no_changes_needed,committed,blocked,failed,inspect. Oncommittedorno_changes_needed, surfaceapi_suggestions/findings_flaggedif either is non-empty — that's the actual point of keeping cleanup an implementer rather than an architect. Any result carryingworktree_dir/cleanup_branch—blocked,inspect, or afailedpast the preflight checks — leaves that worktree and branch fully intact for manual rescue. No automated resume.
2. run-cleanup.sh — Shell launcher
The background script that owns the whole pipeline:
- Validates args and request-file JSON; resolves the checkout's git dir; acquires its own
codex-cleanup.lock(separate from finalize's — they don't contend, since cleanup never touches the checkout's working tree until the final fast-forward). - Preflight, hard-fail rather than reason about it: an in-progress merge/rebase/cherry-pick, or the checkout still being dirty at this point (Step 0 above should have already guaranteed clean) — both return
failedimmediately. BASE= currentHEAD. Creates a throwaway branch + worktree atBASEviagit worktree add— since the checkout was clean,BASEalready contains everything; no untracked-file copying needed.codex exec#1 — cleanup-produce. Checks abnormal exit and a self-reportedstatus: "failed"; otherwise continues regardless of whether it made changes.codex exec#2 — cleanup-critique. Always runs, fresh--ephemeralsession, no shared context with the producer.- Gate:
git status --porcelainin the worktree, checked only now — after both passes. Empty →no_changes_needed, worktree/branch discarded,api_suggestions/findings_flaggedstill threaded into the result. Non-empty → proceed. - Selects
commit_message: the critic's if non-null/non-empty, else the producer's;failedif neither is usable (guards against a literal"null"commit title). Computes the touched-files list viagit diff --name-only -z "$BASE"+git ls-files --others --exclude-standard -z, deduped — rename-safe, unlike a naivegit status --porcelain -z+ fixed-offset substring strip. codex exec#3 —$finalize-commit, the existing skill, completely unmodified. The pipeline's one and only commit.git rebase --onto <current checkout tip> <BASE> <branch>— a pure concurrency guard against something else landing on the checkout mid-run (e.g. a parallel finalize-with-codex run), not WIP-netting. Conflict →blocked,blocked_stage: rebase, worktree/branch left intact.git merge --ff-onlyback into the checkout, retried up to 3 times against a moving tip. Still failing →blocked,blocked_stage: merge_back.- Success → worktree/branch removed,
CLEANUP_DONE $RUN_DIR.
3. cleanup-produce-SKILL.md — the producer
Installed at ~/.codex/skills/cleanup-produce/SKILL.md. Runs inside the isolated worktree, starting from a guaranteed-clean tree.
In scope: reuse, dead code, duplication, naming, over-engineering. Explicitly not bug-hunting.
Scope boundary: the diff/task implied by files/task_summary, plus adjacent code only when the in-scope cleanup requires it — concretely: the same file, the same module, or direct callers of a symbol its own change made dead. Never roam for unrelated cleanup elsewhere.
Exclusion list: reads CLAUDE.md/README first and extracts every explicit "deliberate, do not simplify" statement before touching anything.
Reuse-sweep protocol: for every non-trivial changed block, extracts a distinctive signature and greps the whole repo — not just adjacent modules — for near-duplicates. Extracts only when there are ≥2 real consumers.
Tests: adds/updates tests to match its changes; runs the repo's own documented test commands; reverts a specific change if it breaks a test rather than pushing forward.
Never: changes a public/exported API — records the idea in api_suggestions instead. Never commits; that happens exactly once, later, inside finalize-commit.
4. cleanup-critique-SKILL.md — the critic
Installed at ~/.codex/skills/cleanup-critique/SKILL.md. Runs in a fresh Codex session with no shared context with the producer — the reset is the actual mechanism, not a formality. A real prior finding validated it: a producer-style pass missed a 4-line duplicated Navigation-Compose incantation between two call sites; a separate pass over the resulting diff, unburdened by the producer's own reasoning, caught it.
Always runs, regardless of how much or little the producer changed — an earlier design gated the critic on the producer's own output size, which review found anti-correlated with the risk it was meant to catch: a producer that tunnel-visions and misses something tends to produce a small diff, exactly the wrong signal to skip a second look on.
What "the diff" means (never anything wider): the producer's resulting diff if it made changes; otherwise the diff from pre_cleanup_head to BASE (whatever Step 0 committed on the way in, if anything) — if that's also empty, there's genuinely nothing to review, and "nothing found" is reported as done, not a special case.
Apply vs. flag: applies only high-confidence, cosmetic, in-scope fixes itself, running tests after each and reverting on failure. Flags everything else — structural changes, anything touching outside the diff, anything public-API-shaped — into findings_flagged rather than applying it. Same ≥2-consumer extraction gate as the producer. Never touches a public API itself.
cleanup-produce-schema.json and cleanup-critique-schema.json follow the same flat-nullable-fields convention as finalize-with-codex's own response-schema.json — all fields always present but nullable, since a oneOf-per-status design caused real validation friction against codex exec --output-schema in the original finalize work. The top-level $RUN_DIR/result.json that run-cleanup.sh assembles is hand-built via jq -n on every path and passed through no schema of its own — same pattern as run-finalizer.sh's own failure-path synthesis.
run-cleanup.sh invokes the existing, completely unmodified finalize-commit skill as its one and only commit step (codex exec ... $finalize-commit, referencing finalize-with-codex's own response-schema.json by relative path), and cleanup-with-codex-SKILL.md invokes the full finalize-with-codex Claude skill a second time at its own Step 0, to land any pre-existing dirty work before the worktree pipeline starts. Neither invocation reimplements finalize's rules — this system depends on that gist being installed, not merged into it. The two systems have different risk profiles and update cadences (a fix to finalize-commit benefits both without cleanup's own files changing at all), which is also why they're published as separate gists rather than one.
User: "cleanup this"
│
Claude: git status --porcelain in checkout_path
→ dirty? draft a message, invoke finalize-with-codex, wait
→ capture pre_cleanup_head
→ writes request JSON to scratchpad
→ calls run-cleanup.sh in background
│
run-cleanup.sh (background):
→ validates args/request JSON
→ acquires its own lock
→ preflight: in-progress merge/rebase/cherry-pick? dirty tree? → failed
→ BASE = HEAD; git worktree add -b <branch> <dir> BASE
│
codex exec #1 — cleanup-produce (isolated worktree)
→ reads CLAUDE.md/README, builds exclusion list
→ finds cleanup in scope, repo-wide reuse-sweep before extracting
→ edits + adds/updates tests; reverts a change that breaks a test
→ never touches a public API — records api_suggestions instead
→ authors its own commit_message
│
codex exec #2 — cleanup-critique (ALWAYS runs, fresh session, no shared context)
→ reviews producer's diff, or pre_cleanup_head→BASE if producer did nothing
→ fresh repo-wide reuse-sweep
→ applies only high-confidence/cosmetic/in-scope fixes, tests after each
→ flags everything else into findings_flagged
│
gate: anything actually changed in the worktree?
no → no_changes_needed, worktree/branch discarded,
api_suggestions/findings_flagged still surfaced
yes → select commit_message (critic's, else producer's; fail if neither)
│
codex exec #3 — $finalize-commit (existing, UNMODIFIED skill)
→ format/lint/compile/test → commit
│
git rebase --onto <checkout tip> BASE <branch> (concurrency guard)
git merge --ff-only <branch> into the real checkout (retried up to 3x)
success → worktree/branch removed, CLEANUP_DONE $RUN_DIR
conflict at either step → blocked, worktree/branch left intact
│
Claude: reads result.json
no_changes_needed → good outcome; surface any flagged findings
committed → SHA + message; surface api_suggestions/findings_flagged
blocked → surface blocked_stage + blocker; artifacts intact for rescue
failed / inspect → surface error/log; same inspect discipline as finalize-with-codex
- Worktree isolation, not a stash-and-net-out approach. An earlier design let cleanup run on a dirty checkout, using
git stash createasBASEand relying onrebase --onto's exclusive lower bound to keep WIP out of the landed commit. Review found this left the post-run checkout state genuinely unstated, and — separately — that a stash never captures untracked files, so an unfiltered scope computation could sweep any untracked scratch file into the cleanup commit regardless of whether cleanup touched it. The fix: commit dirty WIP for real (via finalize-with-codex, Step 0) before the pipeline ever starts.BASEis then always justHEADin a checkout guaranteed clean, andrebase --ontonarrows to what it should always have been — a concurrency guard, not a WIP-netting mechanism. - The critic always runs. A gate keyed on the producer's own output size is anti-correlated with the risk it's meant to catch — see cleanup-critique-SKILL.md above. Deterministic, bash-computed gating happens exactly once, and only after both passes: did the worktree actually change at all.
- Fresh session for the critic is the mechanism, not a formality. No shared context with the producer is what let a real prior run catch a cross-file duplication a diff-scoped producer pass structurally couldn't see.
- ≥2 real consumers before any extraction, for both producer and critic — mirrors this system's own home repo's stance that "three similar lines is better than a premature abstraction."
- Flag, don't touch, anything public-API-shaped. The main Claude session stays the architect; the Codex agents stay implementers.
api_suggestionsandfindings_flaggedare threaded all the way from the producer's/critic's own result files into the top-levelresult.jsonon bothcommittedandno_changes_needed— a flagged finding must never be silently discarded once the worktree is torn down. - Block, never auto-resolve, on either a rebase or a merge-back conflict. Both leave the worktree and branch fully intact for manual rescue — no automated resume for v1.
- Deterministic checks throughout, never Codex self-reporting. The touched-files list, the "did anything change" gate, and the commit-message null/empty guard are all computed in bash from git state — never inferred from how a model "feels" about its own output.
- Rename-safe file-list computation.
git status --porcelain -zcorrupts a renamed file's new path — a rename emits two NUL-delimited records and only the first carries a status prefix, so a naive fixed-offset substring strip mangles both.git diff --name-only -zhandles a rename as one clean path. - Two gists, not one, despite
finalize-commitbeing a real runtime dependency of this system. The coupling is an invocation-by-name relationship (this system shells out to$finalize-commitand references its schema by relative path) rather than shared code — the same relationship a script has withgitorgradlew. finalize-with-codex is the thing to run on every commit; cleanup-with-codex is a heavier, occasional pass most people won't want by default. Keeping them separate means a fix to one doesn't show up as noise in the other's version history, and someone can adopt just the commit gate without inheriting a worktree-manipulating pipeline they didn't ask for.
Raised in review, not adopted for v1 — kept here in case cost/experience later justifies building them:
- A formal, deterministic instruction-discovery and precedence algorithm across CLAUDE.md, AGENTS.md, nested instruction files, CONTRIBUTING.md, and build docs, rather than "read CLAUDE.md and the nearest module README(s)."
- Mandating two independent, explicitly logged search strategies for the reuse-sweep (a symbol/call-site grep plus a separate structural/behavior-marker search), with generated-code exclusions and a record of every search performed.
- Worked-example operational definitions for "cosmetic" vs. "structural," "high-confidence" vs. not, and "public/exported" vs. not.
- A configurable policy layer for huge diffs, binary files, generated sources, vendored code, and submodules — default exclusions and size/time thresholds instead of an unbounded pass.