| name | codex-handoff |
| description | Use when interpreting state mutations during plan/review/verdict transitions — explains exactly which fields the bridge writes when, so the implementer can debug 'why is current_task suddenly T3' or 'why is base_sha old'. |
| when_to_use | Debugging unexpected state.json values | wondering why current_task advanced | checking what last_review/last_verdict mean | inspecting bridge artefacts |
| allowed-tools | Bash(autopilot-codex:*), Read, Glob |
AutoPilot Codex — handoff between Claude and Codex
The plugin has two halves: a Node bridge (scripts/codex-bridge.mjs)
that calls Codex and a small set of hooks that drive Claude. They
synchronize through one file: ${CLAUDE_PLUGIN_DATA}/state.json. This
skill documents exactly what each half writes when, so you can debug
"why does state look like this".
Bridge writes (all in cmdPlan / cmdReview / cmdVerdict)
cmdPlan (called by /codex-plan, /autopilot, codex-manager subagent)
Resets the entire run:
state.goal ← user's goal verbatim
state.plan_path ← docs/autopilot/plans/<date>-<slug>.md
state.plan ← full plan JSON from Codex
state.conversation_id ← Codex thread id (or recovered from rollout)
state.current_task ← plan.tasks[0]
state.base_sha ← HEAD of cwd at plan time
state.iteration ← 0
state.no_progress_streak ← 0
state.status ← "running"
state.budget_used_usd ← 0 (resolveBudgetUsd reset)
state.budget_usd ← from --budget / AUTOPILOT_BUDGET_USD / 5.00
state.token_usage.plan ← { input, cached, output, calls: 1 }
cmdReview (called by post-tool-use hook, /codex-review, codex-reviewer subagent)
Records review of current diff against current task acceptance:
state.last_review.{task_id, decision, summary, issues_count,
base_sha, head_sha, timestamp, review_path}
state.token_usage.review.{input, cached, output, calls++}
state.budget_used_usd ← += chargeBudget(MODEL, usage)
It does NOT change current_task, base_sha, status, iteration.
cmdVerdict (called by stop hook, drives the loop)
Advances state per Codex's decision:
state.iteration ← ++
state.last_verdict.{decision, reason, timestamp, verdict_path,
progressed}
state.token_usage.verdict.{input, cached, output, calls++}
state.budget_used_usd ← += chargeBudget(MODEL, usage)
# decision-specific:
if decision == "done":
state.status ← "done"
state.no_progress_streak ← 0
if decision == "abort":
state.status ← "aborted"
state.stop_reason ← verdict.reason
state.no_progress_streak ← 0
if decision == "continue":
state.current_task ← verdict.next_task
state.base_sha ← HEAD at verdict time
if progressed:
state.no_progress_streak ← 0
else:
state.no_progress_streak ← ++
if streak >= threshold: status = "aborted" (forced)
if iteration >= max_iterations:
state.status ← "stopped_iteration_limit"
Hook writes (PostToolUse, Stop, UserPromptSubmit)
Hooks themselves do not write state. They CALL the bridge (bridge review from PostToolUse, bridge verdict from Stop) and forward its
output back into Claude's context. The exception is budget-guard.mjs check, which flips status to stopped_budget when over cap.
Forward-migration on read
Every loadState() call runs migrateBudgetFields(state, env) on the
parsed object. A pre-budget state.json file gains budget_used_usd: 0
and budget_usd: <env or 5.00> automatically without disturbing other
fields. This is idempotent — re-reads are safe.
Why current_task ≠ what you're implementing
The bridge advances current_task only on verdict (one call per
turn). If you implemented T1 and T2 inside the same turn (e.g. dogfood
on the plugin itself), current_task is still T1 when you finish — the
next verdict will see "iteration 0 → 1" but no commits since base_sha
relative to T1, treat it as 'no progress' or auto-advance, depending on
how Codex weighs the diff.
The honest fix when you've drifted: do one commit per task, end your
turn, let the Stop hook run verdict cleanly. That's the path the loop
was built for.
Debugging cheat sheet
node ${CLAUDE_PLUGIN_ROOT}/scripts/codex-bridge.mjs state | jq '{
status, goal, current_task: .current_task.id,
base_sha, iteration, budget_used_usd, budget_usd
}'
ls -t ${CLAUDE_PLUGIN_DATA}/review-*.json | head -1 | xargs cat | jq
ls -t ${CLAUDE_PLUGIN_DATA}/verdict-*.json | head -1 | xargs cat | jq
node ${CLAUDE_PLUGIN_ROOT}/scripts/codex-bridge.mjs stop --reason "manual reset"