A two-agent workflow that separates authoring (Claude) from quality gating (Codex). Claude drafts the commit message and hands off to Codex; Codex runs the checks, makes only mechanical repairs, and commits using Claude's message verbatim.
| File | Purpose |
|---|---|
| finalize-with-codex-SKILL.md | Claude skill — orchestrates the handoff |
| finalize-commit-SKILL.md | Codex skill — runs quality gates and commits |
| run-finalizer.sh | Shell launcher — background bridge between Claude and Codex |
| response-schema.json | JSON schema enforcing Codex's output shape |
The top-level behavioral rule injected into every Claude session. Tells Claude:
- When to use the skill: any change that could affect runtime behavior (source, tests, build config, schemas, CI, deps).
- When to skip it: purely presentational changes (docs, comments, assets,
.gitignore). When ambiguous, skip — it's cheaper to run the skill afterwards than to undo an unnecessary run. - How to hand off: run
git status+git diff+git log, draft the commit message, print it, then invokefinalize-with-codeximmediately. No confirmation step. - While locked: don't edit or stage files in the target checkout. Other checkouts are fine.
- On
blockedresult: surface the blocker; don't bypass the skill. - Never push via Codex. Claude's existing confirm-before-push behavior is unchanged.
2. finalize-with-codex-SKILL.md — Claude skill spec
Installed at ~/.claude/skills/finalize-with-codex/SKILL.md. The model-invocable skill Claude follows step-by-step when it needs to hand off:
- Lock check (advisory): Look for
$GIT_DIR/codex-finalize.lockearly and warn if found — but the shell script's atomic acquisition is authoritative, so this is just an early warning. - Write request file: Use the
Writetool to put{"commit_message": "...", "task_summary": "..."}in the scratchpad. No dynamic text crosses the shell boundary — only the checkout path and file path are passed as arguments. - Launch the helper: Run
run-finalizer.shvia theBashtool withrun_in_background=true. Claude is auto-notified on completion; no polling. - Parse the result: Look for
FINALIZE_DONE $RUN_DIRin the notification output (fall back toFINALIZE_STARTEDif Codex crashed), then read$RUN_DIR/result.jsonand report based on status (committed,blocked,failed,inspect).
3. run-finalizer.sh — Shell launcher
The background shell script that bridges Claude and Codex:
- Resolves the absolute git dir (handles both normal repos and worktrees).
- Atomic lock acquisition via
set -o noclobber— writes PID and start time. Aborts immediately if the lock already exists; never auto-removes stale locks (requires user action). - Creates a unique run dir via
mktemp -d, immediately printsFINALIZE_STARTED $RUN_DIR— so an early crash still tells Claude where to look. - Records
HEAD_BEFORE. - Invokes
codex execwith:-C "$CHECKOUT_PATH"— target working directory-s workspace-write— sandbox level--ephemeral— no persisted session--add-dir "$GIT_DIR"— grants Codex access to the git dir for committing--output-schemapointing toresponse-schema.json— enforces the response shape-o $RUN_DIR/result.json— captures Codex's final message- Prompt via stdin:
$finalize-commit+REQUEST_FILE:context line
- Records
HEAD_AFTER. If Codex exits non-zero or produces invalid JSON, synthesizes a result:inspectif HEAD advanced (commit may have succeeded),failedotherwise. - Prints
FINALIZE_DONE $RUN_DIR. - Lock released by
trapon exit.
4. response-schema.json — Output schema
Passed to codex exec --output-schema to enforce what Codex returns. All fields are present but nullable, keeping the schema simple and compatible with Codex's output handling:
| Field | Type | Notes |
|---|---|---|
status |
enum | committed, blocked, failed, inspect |
commit_sha |
string|null | set on committed and inspect |
commit_message_used |
string|null | set on committed |
repairs_made |
array|null | list of mechanical repairs applied |
blocker |
string|null | set on blocked |
commands_run |
array|null | all commands Codex ran |
error |
string|null | set on failed and inspect |
Design note: An earlier iteration used a
oneOfschema with per-status required fields. This was simplified to flat nullable fields —oneOfcaused validation friction withcodex exec, so all fields are always present but nullable.
5. finalize-commit-SKILL.md — Codex skill spec
Installed at ~/.codex/skills/finalize-commit/SKILL.md. The skill Codex runs when invoked via $finalize-commit. Its constraints are strict:
It does:
- Read
REQUEST_FILEand extractcommit_message+task_summary - Read only build/test docs (not architecture docs) to learn how to run checks
- Run formatter, autofix, lint, compile, tests
- Repair: formatter violations, lint violations, compile errors, import/syntax/rename/type errors
- Rerun all checks after repair
- Stage only intended files and commit with Claude's message verbatim
It never:
- Repairs failing assertions, flaky tests, business logic, public APIs, schemas, deps, or CI
- Rewrites or validates Claude's commit message
- Blocks on documentation inconsistencies, architecture concerns, or instruction disagreements — those decisions were Claude's and the user's, not Codex's
- Pushes, amends, bypasses hooks, disables tests, weakens assertions, or suppresses failures
User: "commit this"
│
Claude: git status + git diff + git log
→ drafts commit message
→ writes request JSON to scratchpad
→ calls run-finalizer.sh in background
│
run-finalizer.sh (background):
→ acquires per-worktree lock
→ prints FINALIZE_STARTED $RUN_DIR
→ invokes codex exec (skill via stdin)
→ validates/synthesizes result.json
→ prints FINALIZE_DONE $RUN_DIR
→ releases lock
Claude: may continue work in other checkouts
auto-notified by run_in_background when done
Codex finalize-commit (concurrent):
→ reads REQUEST_FILE
→ format → lint → compile → test
→ repair (mechanical only) → rerun
→ commit with Claude's message (verbatim)
→ return JSON result
│
Claude: reads result.json
committed → report SHA + continue
blocked → surface blocker to user
failed → show error + log path
inspect → warn user to check git log before retrying
- Request file, not shell args — commit messages can contain quotes, newlines, and special characters. Writing to a JSON file and passing only the path avoids all shell injection risk.
- Atomic lock via
noclobber— prevents two concurrent finalizers on the same checkout. Stale locks require explicit user removal; the script never auto-removes them. FINALIZE_STARTEDbeforecodex exec— ensures Claude has the run dir path even if Codex crashes immediately.- HEAD_BEFORE/HEAD_AFTER comparison — if Codex exits non-zero but HEAD advanced, the commit likely happened;
inspectstatus warns Claude to check before retrying rather than blindly re-committing. --add-dir "$GIT_DIR"— needed to give Codex's sandbox access to the git directory, without whichgit commitinside the sandbox would fail.- Codex blocks only on check failures — architecture disagreements and doc inconsistencies are explicitly excluded as block reasons; Codex is a quality gate, not a reviewer.