From codex-bridge
Delegate substantial implementation, refactor, debug, and review work to OpenAI Codex through a hook-driven runtime. Use when the user says "have Codex…", "run this by Codex", asks for an adversarial review, or wants a plan→execute→review→merge loop. The bridge returns Monitor-ready event envelopes and keeps Codex execution separate from the orchestrator context.
How this skill is triggered — by the user, by Claude, or both
Slash command
/codex-bridge:skillThis skill is limited to the following tools:
The summary Claude sees in its skill listing — used to decide when to auto-load this skill
Codex is the executor; you are the orchestrator. Your job is judgment: when to delegate, what risks to name, when to review, when to iterate.
AGENTS.mdCLAUDE.mdapp-server-broker.mjsconfig.yamlprompts/adversarial-review.mdreferences/brief-composition.mdreferences/command-reference.mdreferences/config-reference.mdreferences/error-recovery.mdreferences/event-schema.mdreferences/monitor-patterns.mdreferences/ndjson-guide.mdreferences/notification-format.mdreferences/orchestration-flows.mdreferences/prompt-writing.mdreferences/state-machine.mdreferences/tag-mapping.mdreferences/templates/coder-mission.mdreferences/templates/research-mission.mdreferences/templates/test-runner.mdCodex is the executor; you are the orchestrator. Your job is judgment: when to delegate, what risks to name, when to review, when to iterate.
Path note: examples use ${CLAUDE_SKILL_DIR}. If it is unset, substitute the install path directly. There is no bare codex-bridge binary.
Claude Code plugin install: when installed as a plugin, prefer /codex-bridge:task, /codex-bridge:review, /codex-bridge:status, /codex-bridge:result, /codex-bridge:events, /codex-bridge:timeline, /codex-bridge:wait, /codex-bridge:send, /codex-bridge:respond, and /codex-bridge:cancel.
Tasks are read-only unless the command opts into writes or config sets a wider sandbox. For file-changing work, use --write; for bridge-managed isolation, pair it with --worktree-auto.
When using --worktree-auto, prompts and brief text must name repo-relative
paths (src/file.ts), not absolute paths inside the launch checkout. Absolute
checkout paths still point at the main workspace, so the bridge rejects them
before creating the task worktree.
Sandbox enforcement: users who pin a sandbox policy can opt into enforcement
that orchestrators cannot silently downgrade with --read-only:
codex_bridge:
sandbox_policy: "danger-full-access"
sandbox_enforce: true
Run /codex-bridge:setup --enforce-sandbox once to install the Claude
permission-layer deny rules. The bridge also denies task --read-only in the
PreToolUse Bash hook when sandbox_enforce: true, and Explore agent reroutes
use --write --worktree-auto instead of --read-only. Known limitations:
auto-pipeline check, standalone review, standalone adversarial-review, and
the stop-time review gate run read-only by design; disable the stop-time review
gate when enforcing sandbox pins.
For N >= 2 parallel jobs in a Claude Code plugin install, use
/codex-bridge:fan-out:
/codex-bridge:fan-out --group <name> --prompt "..." --prompt "..." [--read-only|--write]
Do NOT use Agent { subagent_type: "codex-bridge:codex-bridge-runner" } for
parallel dispatch. The runner is for single substantial handoffs; fan-out uses
direct Bash dispatch and tags every job with the same group.
After dispatch, track the group:
/codex-bridge:status --group <name>
/codex-bridge:wait --group <name> --all
/codex-bridge:bundle --group <name> --output ./audit.tar.gz
Use it for substantial implementation, multi-file refactors, migrations, adversarial review, background coding jobs, and task→review→follow-up loops.
Skip it for quick lookups, single-symbol greps, tiny one-file edits, or foreign long-running commands such as npm test and xcodebuild. Monitor only understands codex-bridge .events files.
The plugin enforces sandbox, plan-mode, Monitor, and event filtering automatically through hooks. You don't think about them. Specifically:
--background task. The exclude tags, timeout, and verbosity come from the workspace config (.claude/codex-bridge.local.md).--mode default.--read-only flags are stripped if the user has set sandbox.enforce: true.To inspect or change any of these: /codex-bridge:config show and /codex-bridge:config set <key>=<value>.
When a hook misbehaves: CODEX_BRIDGE_HOOK_DISABLE=<name> (or =all) bypasses it for one session. See references/troubleshooting.md for the full list.
jobId / task_id (task-mo… / review-mo…) — use for status, result, wait, events, cancel, merge, verdict, and iterate.threadId (UUID v7 019d…) — use for send and steer.Do not pattern-match [codex] Thread ready (019d…) from stderr; that is a threadId. The --json envelope is canonical.
node ${CLAUDE_SKILL_DIR}/scripts/codex-bridge.mjs task --json --mode default "Inspect this bug and propose the smallest fix"
node ${CLAUDE_SKILL_DIR}/scripts/codex-bridge.mjs task --json --write --worktree-auto --background --effort high "Implement the requested change"
node ${CLAUDE_SKILL_DIR}/scripts/codex-bridge.mjs review --json --scope branch
node ${CLAUDE_SKILL_DIR}/scripts/codex-bridge.mjs status --json
node ${CLAUDE_SKILL_DIR}/scripts/codex-bridge.mjs events <jobId> --follow --exclude HEARTBEAT
node ${CLAUDE_SKILL_DIR}/scripts/codex-bridge.mjs respond <req-id> --answer "Use the existing API shape."
Every --json command returns { ok, schema_version, command, result, meta } on success or { ok:false, error } on failure. Use result.next_action.command and result.monitor.tool_hint verbatim. Sync task --json blocks through the entire auto-pipeline (review + completion check). With the default auto_review: true, a prompt with no code work still waits through the reviewer's stage timeout before returning. For interactive or low-latency work: pass --no-pipeline to keep diff capture while skipping review/fix/check, set auto_review: false in config.yaml, or use the async pattern below.
For non-trivial work, prefer a structured brief plus a real prompt. The brief is appended to the worker prompt and persisted with the task for review/check forensics.
{
"goal": "Add retry/backoff to the upstream fetcher",
"worker_assignment": "Implement exponential backoff with jitter, max 3 attempts; preserve the public API; cover with a unit test.",
"specific_concerns": [
"Don't swallow non-retryable 4xx upstream errors",
"Reuse the existing Config object"
]
}
result.next_action.command is already a ready-to-paste invocation in full node /absolute/path/to/codex-bridge.mjs <sub> … form — run it verbatim, no substitution required. The codex-bridge <sub> shorthand only appears in --help text and the printed exit-code doc; it is never written into the JSON envelope. There is no codex-bridge binary on $PATH.
Exit code is the fast gate — branch on $? before parsing:
$? | Meaning | Action |
|---|---|---|
| 0 | success | continue |
| 2 | bad flag / missing arg | fix the command |
| 3 | resource not found | check the id |
| 4 | auth failed | codex login |
| 5 | conflict (already running) | check state |
| 6 | validation (bad input) | fix the input |
| 7 | transient (timeout / network / rate-limit) | retry with backoff |
| 8 | partial success | inspect result / error.partial for details |
| 1 | internal crash | escalate |
Every task follows this lifecycle:
[QUESTION] or produce a [PLAN]. Skip with --mode default.[PLAN] arrives, review and approve or revise.[PIPELINE:diff]→[PIPELINE:diff:done], then optionally [PIPELINE:review]→[PIPELINE:review:done], [PIPELINE:fix]→[PIPELINE:fix:done] files=[a,b,c], [PIPELINE:check]→[PIPELINE:check:done], and finally a terminal [PIPELINE:done] or [PIPELINE:failed]. Use --no-pipeline to keep diff capture but skip review/fix/check.[DONE], [INCOMPLETE], or [ERROR].A synchronous task --json call returns the same lifecycle outcome as a single envelope with result.phase ∈ { plan-pending, done, incomplete, workspace-dirty } and result.next_action.command. result.pipeline.touchedFiles lists files the pipeline's fix stage wrote (empty if no pipeline fixes were applied). A failed Codex turn returns an ok:false error envelope instead (class per the exit-code table above), not a success envelope with a phase: "error" value. Use sync when you don't need interim progress; use async + Monitor when you do.
Important: Codex has its own internal skills that may override plan-mode behavior. It may skip planning and go directly to execution, or ask questions via text instead of the requestUserInput tool. If [PLAN] never arrives and [DONE] appears instead, Codex executed without planning — review the diff and send follow-ups as needed.
Two shipped defaults affect what Codex does — know them before reading Codex output:
sandbox_policy: "danger-full-access" — Codex runs without a sandbox by default. It can write anywhere in the filesystem, including .git/ (so Codex can commit its own work). Opt into stricter profiles via config.yaml (workspace-write restricts to cwd; read-only forbids writes). Pre-1.2.0 default was workspace-write, which routinely triggered Codex to interpret sandbox denials as puzzles (e.g. osascript probes to reach a human terminal).skip_meta_skills: true — an [ORCHESTRATOR DIRECTIVE] is auto-prepended to every prompt telling Codex to skip any internal planning / ceremony / meta-skill chain it would normally walk before execution. Framework-agnostic: covers any skill chain that produces spec or plan scaffolding under paths like docs/, plans/, specs/, or similar before touching the deliverable. Without this, Codex can spend many minutes on that ceremony when the bridge is already orchestrating. Set skip_meta_skills: false if you're running without an orchestrator and specifically want that chain to run.Six independent timeout budgets, each resolved CLI flag → config.yaml key → built-in default. Malformed flag values throw usage (exit 2) rather than silent fallback.
| Phase | Default | Config key | CLI flag |
|---|---|---|---|
| Plan turn | 30 min | turn_plan_ms | --turn-plan-ms |
| Execute turn (also send turns in default mode) | 30 min | turn_default_ms | --turn-default-ms (task) / --turn-timeout-ms (send) |
| Per-stage pipeline (review/fix/check) | 12 min | pipeline_stage_ms | --pipeline-stage-timeout-ms |
| Pipeline total | 30 min | pipeline_total_ms | --pipeline-total-timeout-ms |
| Question unanswered (server request rejected) | 5 min | question_answer_ms | --question-timeout-ms |
| No-event idle (per turn) | 5 min | idle_timeout_ms | --idle-timeout-ms |
Idle fires a [ERROR] … | ClientTimeout with origin: idle (v1.4.1+; pre-1.4.1 this collapsed to origin: turn); pipeline-stage timeouts fire with origin: pipeline:<actualStage> and matching failing_stage: <actualStage>, while PIPELINE_ERROR.lastCompletedStage preserves the last finished stage. If Monitor goes silent and status <id> still reports running past the relevant timeout plus ~60 s buffer, the task is genuinely stuck — cancel <id> recovers.
The .events file is never silent for more than ~60 s during a running turn, and an orchestrator always sees a rich summary at least every 5 min.
[HEARTBEAT] every ~60 s (override: CODEX_BRIDGE_HEARTBEAT_MS). Non-terminal liveness pulse carrying elapsed time, phase, pid, last-item, budget remaining, and a re-attach tail command. Silence past ~90 s means the bridge wrapper is dead — investigate the pid, don't keep waiting.[CHECKPOINT] every ~5 min (override: CODEX_BRIDGE_CHECKPOINT_MS). Non-terminal rich summary: the last assistant message in full, every tool call in the interval with compact parameter previews (Read/Write/Edit paths, commands), git commits landed in that window, a --shortstat diff since the previous checkpoint, and a cumulative since-start diff. Designed so an orchestrator dropping in on a long-running task can catch up from one block instead of scrolling the entire ndjson.[ERROR] | StallDetected. Monitor self-terminates; the orchestrator cancels or steers. Override the threshold with CODEX_BRIDGE_STALL_CHECKPOINTS (integer ≥ 2).[STALL_WARNING]). v2.2.0. Fires at the first barren checkpoint (~5 min default) before the terminal stall fires at 15 min. Non-terminal; gives the orchestrator early warning to steer or cancel. Configurable via stall_warning_threshold_ms in config.yaml or CODEX_BRIDGE_STALL_WARNING_MS env var. Set to 0 to disable.[NEEDS_ATTENTION]). v2.2.0. Emitted alongside [QUESTION], [PLAN], and [ERROR]. At fan-out N, filter NEEDS_ATTENTION across all jobs to find "which jobs need me now?" without merging separate QUESTION/PLAN/ERROR streams.[ARTIFACT]). v2.2.0. Emitted when Codex creates a new file in the worktree (fileChange item with kind "create"). Structured fields: filePath, size_bytes. Gives the orchestrator explicit "artifact landed" events without diffing the worktree.[DRIFT_WARN]). v2.2.0. Fires when Codex touches files outside the inferred prompt scope (ratio > 30% AND > 3 drifted files). Conservative threshold; re-fires if scope is re-established then broken again. Non-terminal — orchestrator should review or cancel.finally block in runBridgeTask verifies a terminal tag landed before the turn returns or throws. If not, it synthesizes [ERROR] | UnhandledExit. That marker is itself a bug report — a turn exited past every instrumented branch; file an issue with the jobId and the events file.Raw-tail escape hatch. The Events dir: / Events file: lines printed in the footer (and result.eventsDir / result.eventsPath in --json) are canonical paths you can tail -f directly, bypassing every bridge subcommand. Useful when the bridge CLI itself is behaving oddly — the file keeps being written as long as the wrapper process is alive.
Tags in the stream fall into two semantic buckets. Orchestrators should handle them differently:
Interrupts — act now. Appear immediately, demand a response:
[QUESTION] — Codex is blocked waiting for an answer; respond via respond <request-id> --answer ….[PLAN] — plan-mode turn produced a plan; terminal for wait/Monitor. Approve via send <thread-id> --mode default "Implement the plan." or revise.[DONE] / [ERROR] / [INCOMPLETE] / [PLAN] — terminal, Monitor self-closes. Branch on the origin line and result.adapterResult.terminalTag from result --json.Progress — periodic scan. Informational; safe to process in batches:
[CHECKPOINT] — primary LLM-facing digest (every ~5 min): last assistant message, tool calls with parameter previews, git commits, diff since last checkpoint. Read these for "what is Codex doing."[HEARTBEAT] — 60-s liveness pulse. Excluded from Monitor by default (would flood LLM context); still written to the .events file for raw-tail users and the 90-s liveness heuristic.[PIPELINE:*] / [PIPELINE:*:done] — auto-pipeline stage markers. Matter for "did the pipeline finish touching files" before you commit or verify.[WARNING] — circuit-breaker hit (e.g. headless-env osascript loop); cancel/steer if needed.[CONFIRMED] — a [QUESTION] got an answer; no action, just lifecycle trace.[STALL_WARNING] (v2.2.0) — early-warning signal before the terminal stall fires; actionable fields: durationMs, remainingMs, last_action.[NEEDS_ATTENTION] (v2.2.0) — composite tag emitted alongside QUESTION/PLAN/ERROR; filter this single tag across all parallel jobs to find which need attention now.[ARTIFACT] (v2.2.0) — a new file was created in the worktree; fields: filePath, size_bytes.[DRIFT_WARN] (v2.2.0) — Codex is touching files outside the inferred prompt scope; fields: drifted_files, drift_ratio, prompt_scope.Unknown tags pass through. v1.4.0's default is --exclude HEARTBEAT, so any tag a future bridge version emits reaches the orchestrator verbatim. Your code should tolerate tags beyond this list — if you see [FUTURE_TAG_V1_5] …, show it and move on; don't assume the vocabulary is closed.
Heads up — [ERROR] is ambiguous: the events-file [ERROR] fires for any turn-level failure, including an auto-pipeline sub-stage timeout, while the sync task --json envelope for the same run can still report ok:true with result.phase: "incomplete" and result.pipeline.error populated. Monitor self-terminates either way; treat [ERROR] as "something broke — read origin: on the error line and result.pipeline.error in the envelope before retrying." Full triage in references/error-recovery.md.
Canonical pattern. Launch with --json, read the envelope, hand result.monitor.tool_hint to Claude Code's Monitor tool. The envelope is the only place the bridge guarantees you see the correct jobId — not the thread UUID that appears in [codex] Thread ready (…) stderr progress.
node ${CLAUDE_SKILL_DIR}/scripts/codex-bridge.mjs task --json --write --background --worktree-auto --brief @brief.json "Implement the task described in the Codex Bridge structured brief."
specific_concerns flows into adversarial review as privileged bias-correction context. Put "watch out for X" there, not in a sprawling worker prompt.
node ${CLAUDE_SKILL_DIR}/scripts/codex-bridge.mjs <subcommand> --help.config show --json prints the merged config and layer diagnostics.events --help; unknown tags are forward-compatible.references/error-recovery.md.references/brief-composition.md.references/monitor-patterns.md.references/notification-format.md.references/troubleshooting.md.node ${CLAUDE_SKILL_DIR}/scripts/codex-bridge.mjs task --json --write --worktree-auto --background \
--brief @brief.json \
"Implement the task described in the Codex Bridge structured brief."
Do not run task --brief @brief.json with no prompt; the CLI rejects empty
task requests. If a task returns [INCOMPLETE], use iterate <task_id> or a
fresh worktree task. Do not use --resume-last --worktree-auto; thread resume
does not imply worktree continuity and the CLI rejects that combination.
--write interacts with two layers — sandbox_policy override wins over the mode-derived default. Under the shipped sandbox_policy: "danger-full-access" default, every turn (plan or default) runs with full filesystem access regardless of --write; plan-mode is a reasoning constraint, not a sandbox one. The "first turn is readOnly" behavior only applies when you've tightened sandbox_policy to "read-only" (or cleared the override so the mode-derived default kicks in) — in that configuration, a plan-mode first turn runs readOnly and --write has no effect until a send <thread-id> --mode default … approves the plan. Pass --mode default on task to skip the plan turn in either configuration. --mode on task --background is also applied — the override flows through the job record into the detached worker.
Fallback when jq isn't available. Rendered (non-JSON) output ends with a one-line footer printed verbatim after Codex's final message:
Job: task-mo5xxxxx-yyyyyy · Events: /Users/you/.codex-bridge/sessions/<threadId>.events · Monitor: node … events task-mo5xxxxx-yyyyyy --follow --exclude HEARTBEAT --timeout-ms 1800000
That footer is your source of truth — do not pattern-match the Thread ready (019d…) line from stderr progress. The footer's Job: field is the jobId.
| Flag | Effect | When to use |
|---|---|---|
--no-pipeline | Keeps diff capture, skips auto-review/fix/check, and reports no-op write tasks as [INCOMPLETE] | You want a single turn and own the verification yourself |
--quiet | Suppresses the [codex] … stderr progress stream | You want a clean console and rely on events --follow or Monitor |
--turn-default-ms <ms> | Override per-turn timeout for execute turns | Large scaffolds that legitimately need >10 min |
--turn-plan-ms <ms> | Override per-turn timeout for plan turns | Long-form planning across many specs |
--pipeline-stage-timeout-ms <ms> | Override per-stage pipeline budget | Very large diffs; native reviewer needs longer than 12 min |
--pipeline-total-timeout-ms <ms> | Override total pipeline budget | Very large runs |
--question-timeout-ms <ms> | How long requestUserInput waits before rejecting the unanswered request | Slow loops / humans deliberating |
--idle-timeout-ms <ms> | Override the no-event idle watchdog | Reasoning-heavy tasks that go quiet between app-server events |
All values are milliseconds; malformed (non-positive / non-numeric) inputs throw usage (exit 2). Example of a scaffold that needs extra execute time:
node ${CLAUDE_SKILL_DIR}/scripts/codex-bridge.mjs task --write --mode default --background \
--turn-default-ms 1800000 --pipeline-stage-timeout-ms 1200000 --pipeline-total-timeout-ms 3600000 --json \
"Bootstrap a complete Xcode project from the plan in ./docs/phase-1.md"
Read the plan, then approve or revise:
# Approve (switches to execution mode)
node ${CLAUDE_SKILL_DIR}/scripts/codex-bridge.mjs send <thread-id> --mode default "Implement the plan."
# Revise (stays in plan mode)
node ${CLAUDE_SKILL_DIR}/scripts/codex-bridge.mjs send <thread-id> "Revise step 2: use token bucket instead"
If Codex uses the requestUserInput tool, a [QUESTION] notification appears with options. Respond with one:
node ${CLAUDE_SKILL_DIR}/scripts/codex-bridge.mjs respond <request-id> --question-id <qid> --answer "<label>"
Note: Codex may ask questions via plain text in its assistant message instead of using requestUserInput. In that case, no [QUESTION] notification appears — the turn completes with the question in the output text. Use send to reply:
node ${CLAUDE_SKILL_DIR}/scripts/codex-bridge.mjs send <thread-id> "Use dark theme with purple accents"
Each stage emits a start tag ([PIPELINE:review]) and a done tag ([PIPELINE:review:done] verdict=approve findings=0). The [PIPELINE:fix:done] line includes files=[a.ts,b.ts] listing exactly what the fix stage wrote — distinct from the diff Codex produced in its execute turn. A terminal [PIPELINE:done] (or [PIPELINE:failed]) closes out the whole pipeline. After [PIPELINE:done] no more bridge-side writes are coming to the workspace.
[DONE] means the turn and the pipeline both reached a terminal state. Before you edit, commit, or move on:
[PIPELINE:done] (or [PIPELINE:failed]). Its presence means no further bridge-side writes are pending. If you see [DONE] without a [PIPELINE:*:done] for a run that had auto_review on, something is off.result.pipeline.touchedFiles (in the task --json envelope) before accepting pipeline-applied changes. Empty list means the entire diff is Codex's own execute-turn work. Non-empty means the auto-fix stage wrote those specific files — inspect each before blind-accepting.send. Commit first, then edit in a separate conversation if needed.xcodegen / prisma generate / protoc / similar, don't re-run the generator and compare diffs — the second run's output is non-deterministic for anything order-dependent. Compile with xcodebuild / cargo build / tsc against the committed tree instead.subagent/codex/<task_id>
branches, run result, review or adversarial-review, record/inspect the
verdict, then use merge <task_id>. Manual git merge subagent/codex/*
is a recovery path, not the normal happy path.cancel <job-id> removes the
bridge-created worktree and subagent/*/<task_id> branch by default. Use
--keep-worktree, --keep-branch, or --keep-all only when you explicitly
need to inspect the cancelled checkout afterward.Common follow-ups:
node ${CLAUDE_SKILL_DIR}/scripts/codex-bridge.mjs reviewnode ${CLAUDE_SKILL_DIR}/scripts/codex-bridge.mjs send <thread-id> "also add tests"node ${CLAUDE_SKILL_DIR}/scripts/codex-bridge.mjs result <job-id>The notification lists missing items. Decide whether to fix them:
node ${CLAUDE_SKILL_DIR}/scripts/codex-bridge.mjs send <thread-id> "Complete the missing items"
The sync envelope may return ok:true, result.phase: "workspace-dirty" when Codex's execute turn produced a non-empty diff but failed to commit (typically SandboxError — workspace-write denies .git/ writes). The diff is intact and actionable; you commit on Codex's behalf, or re-run with config.sandbox_policy: "danger-full-access" (already the shipped default). result.sandboxError has the raw error message.
Emitted when command_failure_circuit_breaker: true (default) detects 3 of 5 same-family command failures (osascript / applescript / open-app / computer-use) — typically Codex flailing in a headless environment. The event carries family, threshold, sample, and turnInterrupted: no (today, logging-only). Monitor does not self-terminate on [WARNING] — the stream keeps flowing. On seeing one, decide:
node ${CLAUDE_SKILL_DIR}/scripts/codex-bridge.mjs cancel <job-id> if the environment genuinely can't run the family; add --keep-all only if you need to inspect a cancelled worktreenode ${CLAUDE_SKILL_DIR}/scripts/codex-bridge.mjs steer <thread-id> <turn-id> "This environment is headless — move on" to redirectEach [ERROR] block carries an origin: line. The canonical vocabulary actually emitted today:
origin: | Cause | First action |
|---|---|---|
idle | No events from Codex for the idle window (upstream silent mid-turn). | relaunch with a larger --idle-timeout-ms. |
upstream:compact-proxy | Remote compact proxy returned 502 ("Proxy request budget exhausted"). | Narrow required-reads, shorten follow-ups. |
upstream:transport | Upstream WS/stream disconnected before turn/completed. Workspace unchanged. | send the same prompt; reasoning is lost but safe to retry. (Bridge auto-retries up to 3× with backoff in v1.5.0+.) |
upstream:response-chain-lost (v1.5.0) | Upstream 400 previous_response_not_found — the resp_id is dead. | New task, not send; seed with committed state. See error-recovery.md#response-chain-lost. Paired with [HANDOFF]. |
upstream:auth (v1.5.0) | Upstream 401 Unauthorized (direct Codex or proxy). | Reauth the right layer (codex login or proxy reauth); do not retry. Paired with [HANDOFF]. |
upstream:invalid-request (v1.5.0) | Upstream 400 invalid_request_error not covered by response-chain-lost. | Bridge auto-retries 3× with backoff. On exhaustion: rebuild prompt, relaunch fresh task. |
turn | Every other turn-level failure. Distinguish by errorCode: ContextWindowExceeded, Unauthorized, SandboxError, generic turn-budget, etc. | See error-recovery.md. |
pipeline:<stage> | Auto-pipeline sub-stage failure. <stage> matches failing_stage:; PIPELINE_ERROR.lastCompletedStage records the previous completed stage. The main task may still have succeeded. | inspect with result, then rerun-review. |
bridge | Bridge safety net fired — indicates a bridge bug. Distinguish StallDetected vs UnhandledExit by errorCode. | File a report with the jobId + events file. |
A pipeline-origin [ERROR] can coexist with a task --json success envelope whose result.phase: "incomplete" and result.pipeline.error are set — read the envelope before retrying. The actions: block inside each [ERROR] is cause-aware and always ends with a see: line pointing to the right anchor in references/error-recovery.md.
For upstream:* origins the bridge runs an exp-backoff retry loop before surfacing the error (policy keyed by origin; see UPSTREAM_RETRY_POLICY in the source). Each retry attempt emits a non-terminal [RETRYING] attempt n/max | origin=… | backoff=…ms block so a reader watching events --follow can tell a slow turn apart from a stalled one.
On retry exhaustion (or immediately for upstream:auth, which has no retry policy), the bridge emits a [HANDOFF] block before the terminal [ERROR]. The handoff surfaces the full continuation context — artifact paths, committed shas, retry history, upstream request id — so another agent (or a human) can pick up where the failed turn left off without hand-reconstructing state from git log. The same payload ships on the JSON envelope under error.handoff. See references/orchestration-flows.md#recovering-from-upstream-state-loss for the consumer recipe.
When commits landed before the error, a [PARTIAL] commits=[…] block precedes [HANDOFF] (and mirrors to error.partial). The bridge snapshots git at turn start and diffs on failure; a non-empty partial.commits list is the cheapest way to answer "did my turn actually do anything before it died?"
Monitor is bound specifically to codex-bridge .events files and their tag vocabulary ([DIRECTIVES], [DONE], [ERROR], [INCOMPLETE], [PLAN], [QUESTION], [CONFIRMED], [PIPELINE:*], [WARNING], [HEARTBEAT], [CHECKPOINT], [PARTIAL], [RETRYING], [HANDOFF]). Re-arming Monitor on a foreign process whose stdout doesn't emit those tags will only ever time out — the filter never matches, Monitor waits the full timeout_ms, then reports stream ended. This wastes orchestrator turns and teaches the agent nothing.
Monitor is single-job. One Monitor call tails one .events file and self-terminates on one terminal tag. For N > 1 parallel Codex jobs, do not stack N Monitor calls — use status --watch for a live table view of all tracked jobs, or await-artifact to block on the specific file each job will produce. See "Running N jobs in parallel" below and references/orchestration-flows.md for the full fan-out / fan-in pattern.
| Situation | Use this |
|---|---|
| Codex task running in the background; you need the terminal tag | Monitor (canonical) |
xcodebuild / npm test / cargo build / pytest / any foreign long command | Bash with run_in_background: true, then poll with BashOutput or wait for the task handle |
| Polling a file for content (not a tag) | Plain shell loop (until [ -s path ]; do sleep 1; done) |
| Watching pipeline's diff-level changes | events --follow --filter PIPELINE (symmetric :done tags as of 1.2.5) |
Rule: if the thing you're watching doesn't write to ~/.codex-bridge/sessions/<threadId>.events with one of the listed tags, Monitor is the wrong tool.
For fan-out workflows, label related dispatches with --group <name> and use group-aware read commands:
# Dispatch a wave with a group label
for prompt in prompts/*.md; do
node ${CLAUDE_SKILL_DIR}/scripts/codex-bridge.mjs task --write --mode default --background --json \
--group audit-2026-05 --prompt-file "$prompt"
done
# Filter status to only the group
node ${CLAUDE_SKILL_DIR}/scripts/codex-bridge.mjs status --group audit-2026-05 --json
# Watch only the group until all jobs terminate
node ${CLAUDE_SKILL_DIR}/scripts/codex-bridge.mjs status --watch --group audit-2026-05
Group names are alphanumeric + hyphens, max 64 chars. Dispatches without --group work exactly as before.
When the orchestrator is fanning out more than one Codex job at a time, Monitor is the wrong primitive (it self-terminates on the first terminal tag of one stream). The right pattern is async-first: launch N background tasks, then block on either status --watch for a live table or await-artifact for a specific file per job.
# 1. Launch N jobs in background; collect their jobIds.
for prompt in prompts/*.md; do
node ${CLAUDE_SKILL_DIR}/scripts/codex-bridge.mjs task --write --mode default --background --json \
--prompt-file "$prompt" \
| jq -r '.result.jobId' >> .jobs.txt
done
# 2a. OPTION A — watch all jobs in one live table (exits when every tracked job is terminal).
node ${CLAUDE_SKILL_DIR}/scripts/codex-bridge.mjs status --watch --interval 10s
# 2b. OPTION B — block on the specific artifact each job produces.
while read -r job; do
node ${CLAUDE_SKILL_DIR}/scripts/codex-bridge.mjs await-artifact "$job" "out/${job}.md" --timeout-ms 900000 --json
done < .jobs.txt
Rules:
result call per job to read the structured outcome (jq '.result.adapterResult.terminalTag, .result.adapterResult.phase, .result.adapterResult.consistent')..events file, and the LLM context can't reason about N parallel streams cleanly.status --watch is the fan-in view; await-artifact is the success-gate per job.node ${CLAUDE_SKILL_DIR}/scripts/codex-bridge.mjs review --scope working-tree
node ${CLAUDE_SKILL_DIR}/scripts/codex-bridge.mjs review --scope branch --base main
node ${CLAUDE_SKILL_DIR}/scripts/codex-bridge.mjs adversarial-review "focus on SQL injection risks"
Day-to-day work rarely needs these; the references have full details.
steer <thread-id> <turn-id> "…" — see references/command-reference.md. Find <turn-id> in the [PLAN] line or the TURN_PARAMS / TURN_COMPLETED NDJSON records.wait <job-id> --timeout-ms 600000 --json returns {jobId, threadId, terminalTag, lastEventLine, elapsedMs, eventsPath}. Exit 7 WAIT_TIMEOUT on deadline.events <job-id> --follow --exclude HEARTBEAT --timeout-ms 1800000. Exclusion-based filter (v1.4.0) means any new tag future bridge versions emit passes through automatically — an inclusion-based --filter X,Y,Z silently drops unknown tags and is not forward-compatible. --filter and --exclude are mutually exclusive. Filter is prefix-aware on the head tag (PIPELINE matches [PIPELINE:review], [PIPELINE:fix:done], …). Continuation lines of multi-line blocks inherit the header's decision, so an included [CHECKPOINT] block ships whole (not just its header). With --json, the closing envelope carries {terminalTag, terminalLine, elapsedMs, filter, exclude} so Monitor can distinguish happy-path close from timeout.timeline <job-id> interleaves .events, .ndjson, the task log, and worker stderr by timestamp. Use --source events, --since <iso-ts>, or --format json|html for narrower incident analysis.summary <thread-id> produces a markdown transcript from the NDJSON log.references/monitor-patterns.md Preset C.Edit ${CLAUDE_SKILL_DIR}/config.yaml to customize behavior. The keys you're most likely to touch:
| Key | Default | Why you'd change it |
|---|---|---|
mode | "plan" | Set to "default" to always skip the plan turn |
auto_review | true | Set to false to skip the auto-review/fix/check pipeline |
sandbox_policy | "danger-full-access" | Tighten to "workspace-write" or "read-only" for stricter runs |
skip_meta_skills | true | Set to false to let Codex run its own planning / ceremony / meta-skill chain before execution (framework-agnostic — any scaffold-producing chain) |
command_failure_circuit_breaker | true | Controls whether [WARNING] fires on osascript / open-app / computer-use flailing |
Six timeout keys (idle_timeout_ms, turn_plan_ms, turn_default_ms, pipeline_stage_ms, pipeline_total_ms, question_answer_ms) — see the matrix in the "Timeout budgets" section above, or references/error-recovery.md for the full flag-to-config mapping.
Use /codex-bridge:config to read or change config without manually editing YAML:
/codex-bridge:config show # see all settings + provenance
/codex-bridge:config set mode=default
/codex-bridge:config set idle_timeout_ms=600000
/codex-bridge:config explain mode
/codex-bridge:config reset # restore plugin defaults
/codex-bridge:config validate # check the workspace config for errors
After any set or reset: restart Claude Code to apply (hooks load at session start).
From the CLI: node ${CLAUDE_SKILL_DIR}/scripts/codex-bridge.mjs config show prints the effective merged config plus which of the four source files are being read. Use it to debug "why isn't my config taking effect?".
Full documentation: references/config-reference.md.
Each task writes artifacts to ~/.codex-bridge/sessions/ (or config.session_dir):
{threadId}.events — Monitor tails this. Tags emitted: [DIRECTIVES] (first event of every turn — non-terminal echo of the effective runtime config: mode, effort, sandbox, approval when set, quiet, skip_meta_skills, pipeline, model when set), [DONE], [ERROR], [INCOMPLETE], [PLAN], [QUESTION], [CONFIRMED], [WARNING], [HEARTBEAT] (every ~60 s during any running turn — non-terminal liveness pulse), [CHECKPOINT] (every ~5 min — non-terminal rich summary: last assistant message, tool calls, git delta, commits since last checkpoint), [PIPELINE:diff|review|fix|check] with matching :done pair, and terminal [PIPELINE:done] or [PIPELINE:failed]. v2.2.0 adds: [STALL_WARNING] (early stall warning after 5 min of no actionable progress), [NEEDS_ATTENTION] (alongside QUESTION/PLAN/ERROR for fan-out routing), [ARTIFACT] (new file created), [DRIFT_WARN] (out-of-scope file touch heuristic). A [ERROR] | UnhandledExit block indicates the bridge's finally-backstop fired — the turn exited without any other error branch emitting a terminal tag. A [ERROR] | StallDetected block indicates 3 consecutive checkpoints (15 min by default) had zero actionable items — Codex is alive but not progressing; orchestrator should cancel or steer.{threadId}.ndjson — Curated retrospective log (turn params, item completions, questions, errors, pipeline stages). Not a full wire mirror. See references/ndjson-guide.md.{threadId}.diff — git diff HEAD snapshot captured by the pipeline.{threadId}.plan.md — Written when Codex emits a structured item/completed with type: "plan".A {threadId}.pending.json / .response.json pair appears transiently while a requestUserInput is in flight (consumed-on-read). {threadId}.review.json is written by adversarial review; [REVIEW] and [PHASE] remain reserved helper formats.
The detached background worker dup's its stderr to <logFile>.worker.err (sibling of the per-job .log). Network errors, rate-limit replies, codex-CLI parser failures, sandbox denials, and process-level crashes land there. The bridge surfaces them two ways so first-pass triage stays in-stream:
[WORKER_STDERR] <threadId> | size=… | delta=… | class=… with the last 500 bytes appended as tail:. Throttled to one block every 30 s with delta aggregation; one final drain runs on terminal so a stack trace written milliseconds before the worker exits still lands. class is a heuristic (network, rate_limit, permission, crash, missing_dependency, unknown) — always read the tail itself when triaging.result --json populates result.adapterResult.workerErr.{path, size_bytes, tail, truncated, error_class_hint} when the file is non-empty. Read the path for full content; tail is bounded to ~500 bytes.# Spot-check after a job fails:
node ${CLAUDE_SKILL_DIR}/scripts/codex-bridge.mjs result <task_id> --json | jq '.result.adapterResult.workerErr'
# Or pull the full stderr file:
cat "$(node ${CLAUDE_SKILL_DIR}/scripts/codex-bridge.mjs result <task_id> --json | jq -r '.result.adapterResult.workerErr.path')"
# Full health check (node, npm, codex, auth, broker runtime)
node ${CLAUDE_SKILL_DIR}/scripts/codex-bridge.mjs setup --json
# Fast auth-only probe
node ${CLAUDE_SKILL_DIR}/scripts/codex-bridge.mjs auth-status --json
# Pin behavior against a specific build / feature set
node ${CLAUDE_SKILL_DIR}/scripts/codex-bridge.mjs version --json
# "Why isn't my config taking effect?" — prints the merged config
# and which of the four source files were read.
node ${CLAUDE_SKILL_DIR}/scripts/codex-bridge.mjs config show
# Status shows a pile of "running" jobs that aren't actually alive?
# Reap orphaned state-file ghosts (status:"running"|"queued" with dead PIDs).
# Idempotent; safe to run repeatedly.
node ${CLAUDE_SKILL_DIR}/scripts/codex-bridge.mjs status --prune-orphans --json
# Broader health report: stale jobs, orphan task worktrees/branches, old
# session files, disk usage, and Codex CLI/auth status. Add --clean --yes
# for non-interactive cleanup; dirty worktrees require --force.
node ${CLAUDE_SKILL_DIR}/scripts/codex-bridge.mjs doctor --json
# CLI exited with status 1 and no obvious error? Check the crash log.
ls -lt ~/.codex-bridge/crashes/ | head -5
# Each file is a JSON dump of the unhandled rejection / exception that
# produced the exit, including argv, cwd, nodeVersion, and the error stack.
# Auto-update: every `bridge task` / `send` / `result` / etc. invocation
# checks for a new release (anonymous, 1 h cache) and — if one exists —
# spawns `npx skills@latest add …` in the background with stdio routed to
# ~/.codex-bridge/auto-update.log. Rate-limited to one attempt per hour.
# The current call is not blocked; the NEW files land before your NEXT
# invocation. Opt out with CODEX_BRIDGE_NO_UPDATE_CHECK=1.
# Check auto-update history / last-attempt outcome
tail -20 ~/.codex-bridge/auto-update.log
# Force a fresh check (bypass the 1 h cache) and print current status
node ${CLAUDE_SKILL_DIR}/scripts/codex-bridge.mjs update --force
# Force immediate install synchronously (waits for npx to finish)
node ${CLAUDE_SKILL_DIR}/scripts/codex-bridge.mjs update --apply
version --json reports the bridge capability list plus the active backend (active_backend) and its declared adapter_capabilities; prefer those fields over prose assumptions when choosing backend-dependent flows. It resolves the backend through --backend, CODEX_BRIDGE_BACKEND, cwd config, workspace config, then skill/user config.
When a command fails, read $? first. Exit 4 means re-auth; exit 7 means retry with backoff (check error.code — ClientTimeout branches by origin: per references/error-recovery.md); exit 2/6 means fix the invocation before anything else.
build.db I/O errorsWhen running xcodebuild from inside a Claude Code session on macOS, the sandbox around DerivedData intermittently returns disk I/O error on the build database. Fix by putting DerivedData outside the workspace: xcodebuild -derivedDataPath /tmp/<project>-dd …. Don't use the default workspace-side DerivedData/ from inside Claude Code.
| File | When to read |
|---|---|
| command-reference.md | Full command and flag documentation |
| monitor-patterns.md | Monitor presets for different scenarios |
| notification-format.md | Exact format of each notification tag |
| ndjson-guide.md | How to parse NDJSON with jq |
| orchestration-flows.md | End-to-end flow diagrams |
| state-machine.md | How result --json derives terminal state |
| error-recovery.md | Error types and recovery strategies |
| config-reference.md | YAML configuration options |
| prompt-writing.md | Writing effective Codex prompts |
When in doubt, ask the runtime first, then read prose. Prose ages; the runtime is canonical.
Creates, edits, and optimizes skills for Claude Code, including drafting, evaluating with test prompts, iterating on performance, and improving skill descriptions for better triggering accuracy.
npx claudepluginhub yigitkonur/codex-bridge