Keep Codex tasks running until they actually finish β by time, rounds, or independently confirmed goal.
codex-loop is a local-first Codex CLI and plugin that keeps explicitly activated Codex tasks running until they satisfy a minimum duration, a target number of deliberate rounds, or an independently confirmed goal.
It is designed for release-grade QA, long-running hardening passes, and repeated review loops where stopping after the first apparently complete answer is not enough.
- Three loop modes. Pick the limiter that fits the work: minimum duration (
min="6h"), deliberate round count (rounds="3"), or independently confirmed goal (goal="ship only after verification"). - Activation by header, not flag. Loops only start when the prompt's first line contains a structured
[[CODEX_LOOP ...]]header, so day-to-day Codex use is untouched. - Independent goal confirmation. Goal loops invoke a configurable headless reviewer that returns normal text, then codex-loop privately interprets that text into a structured verdict.
- Pre-continuation context hook.
pre_loop_continueruns right before each automatic continuation so the next prompt can carry fresh local context β test summaries, changed files, build status, custom checklists. - Codex lifecycle integration. Ships as a Codex plugin, contributing
UserPromptSubmitandStophooks, and mirrors managed registrations into~/.codex/hooks.jsonfor current Codex builds. - Local-first state. Loop state lives under
~/.codex/codex-loop/, isolated by Codexsession_id. Compact verdict metadata lands in~/.codex/codex-loop/runs.jsonl. - Single Go binary. No Python runtime, no daemon, and no built-in network client except the explicit
codex-loop upgraderelease download path. User-configured continuation and goal commands run as external tools.
go install github.com/compozy/codex-loop/cmd/codex-loop@latest
codex-loop installFor an exact release:
go install github.com/compozy/codex-loop/cmd/codex-loop@v0.1.1
codex-loop install
codex plugin marketplace add compozy/codex-loop --ref v0.1.1
codex plugin marketplace upgrade codex-loop-pluginsFor an existing install, codex-loop can perform the release download, checksum verification, managed runtime refresh, and Codex marketplace refresh in one command:
codex-loop upgrade # latest GitHub release
codex-loop upgrade --version v0.1.1codex-loop install creates or updates:
~/.codex/codex-loop/bin/codex-loop~/.codex/codex-loop/loops/~/.codex/codex-loop/config.toml~/.codex/hooks.jsonwith managedUserPromptSubmitandStophook registrations~/.codex/config.tomlwithfeatures.codex_hooks = true
Register this repo as a Codex plugin marketplace:
codex plugin marketplace add compozy/codex-loopFor local development from this checkout:
codex plugin marketplace add /path/to/codex-loopThen restart Codex, open the plugin directory, and install codex-loop from the Codex Loop Plugins marketplace.
The plugin contributes lifecycle hooks from plugins/codex-loop/hooks/hooks.json. codex-loop install mirrors the same managed hook commands into ~/.codex/hooks.json so current Codex builds execute them reliably while preserving unrelated user hooks.
The activation header must be the first line of the prompt. The task prompt starts on the line after the header.
[[CODEX_LOOP name="release-stress-qa" min="6h"]]
Run release-grade QA for this repository and keep expanding scope until the minimum duration is met.
[[CODEX_LOOP name="release-stress-qa" rounds="3"]]
Run three deliberate QA passes for this repository and treat each stop as the end of one round.
[[CODEX_LOOP name="release-stress-qa" goal="ship only after real verification"]]
Run release-grade QA for this repository and keep going until the work is actually complete.
[[CODEX_LOOP name="release-stress-qa" goal="ship only after real verification" confirm_model="gpt-5.5" confirm_reasoning_effort="xhigh"]]
Run release-grade QA for this repository and keep going until the work is actually complete.
| Field | Required | Notes |
|---|---|---|
name |
yes | Loop identifier |
min |
one of | Duration: 30m, 30min, 1h 30m, 2 hours, 45sec, etc. |
rounds |
one of | Positive integer |
goal |
one of | Free-form verification goal; goal="" reuses the task prompt as the goal |
confirm_model |
only with goal |
Model for the goal confirmation run |
confirm_reasoning_effort |
only with goal |
One of minimal, low, medium, high, xhigh |
Loop state is isolated by Codex session_id. Exactly one limiter β min, rounds, or goal β is required.
codex-loop install
codex-loop upgrade
codex-loop upgrade --version v0.1.1
codex-loop status
codex-loop status --all
codex-loop status --session-id <id>
codex-loop status --workspace-root <path>
codex-loop uninstall
codex-loop versionstatus prints JSON. By default it shows only active loops; use --all to include completed, superseded, and cut-short loops.
~/.codex/codex-loop/config.toml supports:
optional_skill_name = ""
optional_skill_path = ""
extra_continuation_guidance = ""
[hooks]
stop_timeout_seconds = 2700
[goal]
confirm_model = "gpt-5.5"
confirm_reasoning_effort = "high"
confirm_command = "codex exec --cd $WORKSPACE_ROOT --ephemeral --yolo --output-last-message $CONFIRM_OUTPUT_PATH $MODEL_ARGV $REASONING_ARGV --skip-git-repo-check -"
timeout_seconds = 2400
interpret_model = "gpt-5.4-mini"
interpret_reasoning_effort = "low"
interpret_timeout_seconds = 120
max_output_bytes = 12000
[pre_loop_continue]
command = ""
cwd = "session_cwd"
timeout_seconds = 60
max_output_bytes = 12000optional_skill_nameandoptional_skill_pathare used only when the path resolves inside the active workspace.optional_skill_pathmay point to a skill directory or directly toSKILL.md.extra_continuation_guidanceappends extra text to every automatic continuation.hooks.stop_timeout_secondscontrols the managed CodexStophook timeout written bycodex-loop install; reruncodex-loop installand restart Codex after changing it.
Goal loops run a configurable headless confirmation command inside the Stop hook before deciding whether to continue. The public confirmation command returns normal text. codex-loop then runs a private interpreter command that converts that text into the internal verdict JSON.
The default confirmation run uses codex exec --yolo, gpt-5.5, and model_reasoning_effort = "high". Model and reasoning are separate settings; use confirm_model = "gpt-5.5" plus confirm_reasoning_effort = "xhigh" rather than a combined model string. Codex documents --yolo as full access without sandboxing or approvals; use a custom confirm_command when you need a different safety profile or runner.
The interpreter always uses codex exec --sandbox read-only --output-schema. Its model defaults to gpt-5.4-mini and reasoning effort defaults to low; users can change only the interpreter model, reasoning effort, and timeout. The interpreter command itself is intentionally not configurable so codex-loop can rely on Codex structured output and the user's existing Codex auth.
Runtime behavior:
confirm_commandis a shell-like string parsed to argv and executed without an implicit shell.- Placeholder values are shell-quoted before parsing so injected values remain literal arguments.
- The default confirmation command runs with
--yolo,--ephemeral, and--output-last-message; it does not receive an output schema. - Custom confirmation commands may write normal prose to
$CONFIRM_OUTPUT_PATHor stdout. - The interpreter command is fixed to
codex execand produces the structured verdict internally. - If the interpreted verdict is complete, codex-loop marks the loop completed and emits no continuation prompt.
- If the interpreted verdict is incomplete, invalid, timed out, or either command fails, codex-loop continues with a warning and keeps the loop active.
goal.timeout_secondscontrols the confirmation timeout;goal.interpret_timeout_secondscontrols the interpreter timeout. Both are normalized to leave time before the outer Stop hook timeout.goal.max_output_bytescaps captured confirmation and interpreter output used for prompts and diagnostics.- Each confirmation attempt appends compact metadata to
~/.codex/codex-loop/runs.jsonl.
Confirmation command variables:
$PROMPT,$PROMPT_FILE, and$CONFIRM_OUTPUT_PATHexpose the reviewer prompt and plain-text output file.$MODEL_ARGVexpands to--model <model>when a model is configured;$REASONING_ARGVexpands to--config model_reasoning_effort="<effort>"when reasoning is configured.$MODEL,$REASONING_EFFORT,$WORKSPACE_ROOT,$CWD,$SESSION_ID,$LOOP_NAME,$LOOP_SLUG,$RUNS_LOG_PATH, and$CODEX_HOMEare also available.- The same values are exported with the
CODEX_LOOP_CONFIRM_prefix.
Custom runner example:
[goal]
confirm_model = "opus"
confirm_reasoning_effort = ""
confirm_command = "compozy exec --ide claude --model $MODEL $PROMPT"pre_loop_continue is a codex-loop runtime hook that runs inside the managed Stop hook, immediately before codex-loop asks Codex to continue the active loop. For goal loops, it runs only after goal confirmation decides another round is needed. It is not a separate Codex lifecycle hook, which matters because independent Codex Stop hooks may run concurrently and cannot guarantee ordering.
Use it when the next continuation prompt should include fresh local context computed at stop time, such as a test summary, changed-file summary, issue tracker snapshot, custom QA checklist, or local build status.
Runtime behavior:
- It runs only when codex-loop has decided to continue the task.
- It does not run when there is no active loop, when the loop has completed, or when codex-loop cuts the loop short.
commandis a shell-like string parsed to argv and executed without an implicit shell. Point it at a script, or explicitly usebash -lc '...', if you need shell features such as pipes or redirects.cwd = "session_cwd"runs from the same directory Codex reported in theStophook payload. This is the default.cwd = "workspace_root"runs from the root codex-loop resolved from.gitor.codex.- The command receives structured JSON on stdin.
- The same JSON is available through
$INPUT_JSONand$INPUT_FILE. - Only stdout is appended to the next continuation prompt under
pre_loop_continue output:. - Stderr is not injected into the prompt. Failures and timeouts keep the loop running and append a short
pre_loop_continue warning:instead. max_output_bytescaps captured stdout before prompt injection.
Example config:
[pre_loop_continue]
command = ".codex/scripts/loop-context.sh --format markdown --input $INPUT_FILE"
cwd = "session_cwd"
timeout_seconds = 30
max_output_bytes = 8000Example script:
#!/usr/bin/env bash
set -euo pipefail
payload="$(cat)"
session_id="$(printf '%s' "$payload" | jq -r '.session_id')"
round="$(printf '%s' "$payload" | jq -r '.loop.continue_count')"
printf 'Session: %s\n' "$session_id"
printf 'Continuation round: %s\n\n' "$round"
printf 'Changed files:\n'
git status --short
printf '\nRecent test signal:\n'
if [ -f .codex/last-test.log ]; then
tail -n 40 .codex/last-test.log
else
printf 'No .codex/last-test.log found.\n'
fiWith that config, every automatic continuation prompt will include the script stdout after codex-loop's normal continuation instructions. If the script exits non-zero or exceeds timeout_seconds, codex-loop still continues the loop and injects a short warning instead of the script output.
codex-loop uninstallThis removes only ~/.codex/codex-loop/. It leaves ~/.codex/config.toml and Codex plugin install state unchanged. It also removes only the codex-loop-managed hook registrations from ~/.codex/hooks.json, preserving unrelated user hooks.
make deps # Tidy and verify modules
make verify # Full pipeline: fmt β vet β lint β race tests β buildRelease tooling mirrors the project CI:
make release-check # Validate .goreleaser.yml with current GoReleaser v2 CLI
make release-snapshot # Build local snapshot artifacts under dist/ (no publish/sign/SBOM)- GitHub Actions runs
make verifyon pushes and pull requests tomain. - Pushing normal changes to
mainruns the release workflow in release-PR mode. It usesgithub.com/compozy/releasepr@v0.0.21to calculate the next semantic version, generateCHANGELOG.md, generate the currentRELEASE_BODY.md, update historicalRELEASE_NOTES.md, sync the Codex plugin manifest version, and open or update arelease/vX.Y.Zpull request. - Release pull requests run CI plus a GoReleaser dry-run check before merge.
- Merging a release pull request to
maincreates thevX.Y.Ztag and publishes the GitHub release through GoReleaser usingRELEASE_BODY.md. - The release workflow requires a
RELEASE_TOKENsecret with permission to push release branches/tags, open pull requests, and dispatch workflows.
Manual release notes can be staged before the release PR is generated:
go run github.com/compozy/releasepr@v0.0.21 add-note --title "Short title" --type featureGenerated note files live in .release-notes/. The release PR archives consumed notes under .release-notes/archive/vX.Y.Z/ and keeps .release-notes/.gitkeep in place for future notes.
codex-loop itself does not send data to a network service. It reads Codex hook JSON from stdin, writes hook decisions to stdout, and stores loop state locally under ~/.codex/codex-loop/.
Goal loops intentionally invoke the local codex exec command for default confirmation and interpretation, which uses the user's configured Codex provider/auth. The local JSONL log stores compact verdict metadata only; it does not store the full original task prompt, full assistant message, confirmation prompt, confirmation review text, interpreter prompt, or pre_loop_continue output.