Codex Crew — Enlisting Codex Agents
Codex agents (OpenAI Codex CLI, run via the codex plugin's codex-companion.mjs runtime) are external crew a coordinator can enlist alongside the claudius roster. Opt-in. Read once before the session's first Codex dispatch.
Dispatch directly, never through codex:codex-rescue. For coordinator-orchestrated work that subagent is pure overhead: one Bash call forwarding stdout unchanged — no monitoring, no analysis, unreliable lifecycle signals (idle_notification, teammate shutdown, stall tracking) layered over a worker that is already a detached Node process with its own job-state files — and it never exposes --cwd/--prompt-file, the root cause of most bugs below. Call codex-companion.mjs task directly (§ Direct Dispatch): nothing to spawn, track, or shut down. Reserve codex:codex-rescue for the user-typed /codex:rescue interactive command, which this skill doesn't govern.
When to Enlist Codex
- Opt-in, not default — when the user asks, or per the coding preference below.
- Coding-first (project default): code-writing work prefers Codex Astra over Opus-tier claudius agents (
developer-bilby) — an intentional override ofdelegate's Token Economy tiering for implementation tasks. - Non-coding roles keep normal tiering: review, QA, security, architecture, and docs stay with claudius agents unless the user opts them into Codex.
Routing — Model Selection, High Effort
- Default: Codex Astra =
--model gpt-6-astra --effort high. Always high effort. State both flags on every dispatch — omitting either drops to the runtime default, not Astra. Astra's rollout is gated behind OpenAI's Trusted Access Programme; confirm account access before assuming it resolves. - Security-related dispatch:
--model gpt-daybreak-blue-latest --effort highinstead of Astra, when available. Applies to any Codex task that is itself security work — security audits/reviews, auth/crypto/secrets handling, vulnerability triage or remediation, dependency security review — not to ordinary feature/bugfix code that happens to touch an authenticated endpoint. Its availability isn't confirmed; treat "when available" literally: if the dispatch's job record showsstatus: failedwith an unknown-model or access-gate/auth error, redispatch the same prompt on Astra instead and tell the user the fallback happened. Never silently retry Daybreak Blue more than once per dispatch. - Dispatch via
codex-companion.mjs taskdirectly (§ Direct Dispatch). Nothing monitors, polls, or fetches results on its own — that's coordinator work (§ Monitoring). Codex CAN attempt a commit when the prompt instructs it, but success is inconsistent; verify independently (Sandbox & Workdir rule 2). - The lighter
sparkalias (gpt-5.3-codex-spark) exists; claudius standardizes on Astra (Daybreak Blue for security work) at high effort.
Direct Dispatch
Resolve the installed codex plugin's script root once per session — version-pinned cache dirs shift on plugin updates, never hardcode a version:
CODEX_ROOT=$(find ~/.claude/plugins/cache/openai-codex/codex -maxdepth 1 -mindepth 1 -type d | sort -V | tail -1)
Write the prompt to a file — never inline it as a shell argument (long prompts with nested quotes or Rust Debug dumps corrupt under shell quoting). task accepts --prompt-file <path> (also reads piped stdin); a relative path resolves against --cwd, so always pass an absolute path under the coordinator's configured scratch location (e.g. /data/tmp on this host).
Every dispatch prompt must say near the top: "You are a leaf worker, not a coordinator — do not load or follow grand-admiral, delegate, track-minions, report-format, or severity; those apply only to sessions that spawn/manage other agents, which you are not."
node "$CODEX_ROOT/scripts/codex-companion.mjs" task \
--cwd <worktree-abs-path> \
--prompt-file <scratch-abs-path>/<descriptive-name>.txt \
--write --background \
--model gpt-6-astra --effort high
Security-related task: swap the model flag, same shape (--model gpt-daybreak-blue-latest --effort high); fall back to gpt-6-astra per § Routing if the job fails with an unknown-model/access-gate error.
--cwd <worktree-abs-path>binds the broker/workspace slug to the intended worktree — pass it on every dispatch; never rely on the invoking shell's cwd or on prompt text telling Codex tocd(prompt text has zero effect on cwd resolution — rule 3).--writeis not implied — without it the run is silently read-only (reports normal completion, touches zero files).--backgroundreturns a job id almost instantly; the coordinator polls job state (§ Monitoring a Codex Job) rather than blocking.- Continuing a thread:
--resume-last(=--resume) with the identical--cwd— threads are found by workspace, so a mismatched--cwdresumes nothing.
Plan-Approval Gate
Sandbox write mode is pinned when the Codex app-server creates a thread; a resume cannot escalate it. Reproduced twice: a thread first dispatched without --write stayed read-only under --resume-last --write, apply_patch rejected. Never plan without --write then resume with it.
For a well-scoped task that might write anything, dispatch the first turn with --write. For a genuine approval gate on large or risky work:
- Dispatch a read-only investigation and plan without
--write. - After approval, start a fresh job (never
--resume-last) with--write, embedding the approved plan plus any revisions in its prompt.
The fresh job rebuilds context, but is the only safe read-only-to-writable boundary. grand-admiral § Development-Work Delegation remains the source of truth for the coordinator's plan review.
Sandbox & Workdir — The Load-Bearing Rules
Codex runs under sandbox_mode = "workspace-write" (see ~/.codex/config.toml). Three rules:
Write scope = cwd + the effective
writable_roots. Rely on the configured worktree root ($CLAUDIUS_WORKTREE_ROOT; seegrand-admiral§ Worktree Isolation), scratch location, and shared cargo target dir, plusnetwork_access = true. Do not assume the configured artifacts location is writable even when config lists it; use the coordinator-owned delivery path inreferences/sandbox-and-recovery.md§workspace-writeConfig. Paths outside cwd and the effective roots are read-only.scripts/cargo-cached.shhandles its verification ledger itself: when the default~/.cache/claudius/ledgerroot is unreachable in-sandbox it falls back to a workspace-local dir (the script is the source of truth).Codex
git commitin a linked worktree is inconsistent — confirmed both ways, same repo, same day. One dispatch committed cleanly, no approval prompt; a later one hit the "Git metadata is read-only"/index.lockerror and the coordinator committed instead.writable_rootswas unchanged across both, so the gate isn't a static config value (suspectedapproval_policy = "on-request"+trust_level = "trusted"interaction — unconfirmed). Coordinator-commit is the reliable default, not a fallback: fine to instruct Codex to attemptgit add/git commitas its final step (with an explicit commit message — it doesn't know your conventions), but plan for failure: verify viagit log/git statusin the worktree — never trust Codex's self-report — and commit yourself (unsandboxed) when it didn't land. Seereferences/sandbox-and-recovery.md§ Git Commit in a Linked Worktree.All worktrees live at
<worktree-root>/<slug>($CLAUDIUS_WORKTREE_ROOT; built-in default/data/git-worktrees, override per host; slug derived from the startup$PWD), pre-created by the coordinator pergrand-admiral§ Worktree Isolation. The broker keys offcodex-companion.mjs's own resolved cwd, not any path in prompt text — confirmed: a dispatch told via prompt tocdinto a pre-created worktree still bound its broker to the coordinator's plain checkout, blocking ALL writes (even underwritable_roots, even on the FIRST dispatch). Pass the worktree via--cwdinstead (§ Direct Dispatch). Each dispatch's--cwdis self-contained, so N worktrees can be dispatched genuinely concurrently — noEnterWorktree/ExitWorktreeserialization.
Deep mechanics (exact sandbox modes, on-disk job-state layout, worktree-commit status and fallback) are in references/sandbox-and-recovery.md.
Never Dispatch Concurrently to the Same --cwd
Never fire dispatch N+1 at the same --cwd until dispatch N's job JSON shows a terminal status (completed/failed). The broker/workspace slug is keyed off --cwd, so distinct worktree paths don't collide; the risk is two dispatches at the same --cwd (a read-only plan job then a fresh writable job, or a retry). Confirmed: same-cwd dispatches minutes apart still collided — elapsed time and a prior dispatch already having its own job-state file are NOT protective; only polling for terminal status is. A collision either strands the earlier dispatch at status=running forever with no completion signal (silent orphan), or instantly returns Codex's generic capabilities boilerplate with touchedFiles: [] (looks like a trivial done, isn't). Root cause — one broker per workspace slug, not per job — lives in the openai-codex plugin and cannot be fixed from this repo.
Mitigation: poll for terminal status before the next same-cwd dispatch (never a fixed stagger delay). scripts/minion-monitoring.py should eventually catch a stuck orphan as CODEX_STALL reason=no-progress — a detection backstop, not a substitute. After any dispatch, sanity-check the job's workspaceRoot matches the intended worktree and its rawOutput actually engages the dispatched task — a suspiciously fast, generic-sounding completion is a collision red flag, not evidence the task was trivial.
Monitoring a Codex Job
MCP watchdog covers Codex too (runtime: codex_cli/codex_companion in register_session) — prefer it over the CODEX_* machinery below when available (see grand-admiral § Recovery → MCP Watchdog), with the same corroborate-before-acting caution.
Direct dispatch has no agent lifecycle to watch — by design. A --background dispatch is a detached Node process with on-disk job-state files: no subagent, no idle_notification, nothing to shut down. Go straight to the job-state file. (If codex:codex-rescue is ever in play — the interactive /codex:rescue command — treat its idle_notification as worthless in either direction: confirmed 4-for-4 in one wave, jobs sat completed 40–85 minutes before the wrapper reported.)
Primary method: read the job's on-disk state directly (mtime-gated, minimal-field reads — never the full state blob). See references/sandbox-and-recovery.md § On-Disk Job State for the field list, result.rawOutput/result.touchedFiles usage, and matching jobs to dispatches. Load-bearing, not a fallback — it is what actually recovers status/results when the stall watchdog can't.
Get notified, don't just poll on request. Arm a Bash run_in_background loop on the job's own state/<workspace-slug>-<hash>/jobs/<job-id>.json (resolve the path per § On-Disk Job State) — a single, job-specific completion signal needing no team/session discovery. For a populated pid, cross-check liveness with ps -p <pid> on every poll; a missing process while the record still says running is a crash signal, not a healthy job:
while true; do
read -r status pid < <(python3 -c "
import json
try:
d = json.load(open('state/<workspace-slug>-<hash>/jobs/<job-id>.json'))
except Exception:
d = {}
print(d.get('status', 'unknown'), d.get('pid') or '')
") || { sleep 20; continue; }
case "$status" in
completed|failed|cancelled|canceled) exit 0 ;;
esac
if [ -n "$pid" ] && ! ps -p "$pid" >/dev/null; then
echo "Codex job <job-id> reports $status but pid $pid is not alive" >&2
exit 1
fi
sleep 20
done
The loop is itself a backgrounded Bash call and inherits the silent-kill risk of § Harness Kills of a Backgrounded Task — periodically confirm it's alive; silence is not health.
ScheduleWakeup is not a substitute — it's /loop dynamic-mode-only and errors outside that context.
- The built-in stall watchdog (
grand-admiral§ Recovery → Built-in Stall Watchdog,scripts/minion-monitoring.py) discovers Codex jobs and emitsCODEX_*transition events when the MCP watchdog isn't in use. A watchdog — MCP or built-in — is mandatory whenever any agent, Claude or Codex, is dispatched (seegrand-admiral§ Spawning → Monitoring). TreatCODEX_*events as best-effort, layered on top of the direct job-state check — never a substitute. - Codex discovery requires
--worktrees— a direct dispatch is never a teammate, so--worktreespointed at the configured worktree root is the only way the built-in watchdog sees it. Without it: a one-time startup warning, then silently zero Codex monitoring. - Direct discovery (
--worktrees/Source C) bypasses the session gate entirely — a workspace under the worktree root surfaces every job'sCODEX_*events regardless ofsessionId. The strict single-session match applies only to ambient discovery (a workspace reachable solely via team lead/member cwd, not also under the worktree root) —codex-companion.mjsstamps each job'ssessionIdfrom its own dispatching session, never the coordinator's, so the ambient path can under-report on a mismatch. The direct job-state check is unaffected either way — which is why it's primary. - Don't guess the Monitor's
--session-id: derive--team-dirfrom a spawn's ownagent_idpergrand-admiral'sreferences/stall-watchdog.md(linked from § Recovery → Built-in Stall Watchdog).
Recovering a Stale Broker
Each worktree dispatch spins up its own broker (app-server-broker.mjs bound to /tmp/cxc-<id>/broker.sock, --cwd = the worktree). Removing and recreating a worktree at the same path while its broker still runs strands it: the next task dispatch against that path fails instantly (~0 s) with a misleading auth-shaped error (failed to resolve feature override precedence / auth.loggedIn: false).
Recovery: find the orphaned broker PID (its --cwd points at the old worktree path), kill it, rm -rf /tmp/cxc-<id>, then redispatch — a fresh broker binds automatically. Exact commands: references/sandbox-and-recovery.md § Broker Recovery.
Additional Resources
references/sandbox-and-recovery.md— sandbox modes,workspace-writeconfig, on-disk job-state layout for monitoring, git-commit-in-a-worktree status (inconsistent) and its fallback, harness kills of a backgrounded task and how to resume, and copy-paste broker-recovery commands.