codex-loop is an external supervisor for Codex CLI (OpenAI's terminal coding agent). You give it a goal; it scaffolds
a local task queue, then keeps running Codex — one task at a time — until your tests
pass or the loop hits a real blocker.
Typical use: you have a coding task too big for a single prompt. You want Codex to keep working on it while you do something else, and stop only when it is actually done.
Not a good fit for: quick one-off edits, questions, or tasks with no clear pass/fail test.
For those, just use Codex directly.
(If your project has no tests yet, you can still use codex-loop — set "verification": { "commands": [] } in codex-loop.yaml and the loop runs until Codex declares all tasks done.)
- One-time setup: trust your project directory and the worktree parent in
~/.codex/config.toml(details in Prerequisites). codex-loop init --prompt "..."— turns your goal into local files: a spec, a plan, numbered task documents, and a config (codex-loop.yaml).- Commit those files so Codex can see them:
git add -A && git commit -m "add codex-loop files" codex-loop run— works through each task in order, running Codex and your verification commands after every iteration. Stops when everything passes, or when it is genuinely stuck.git merge codex-loop/<branch>— merge the changes into your main branch when the loop completes. (codex-loop runprints the exact command when it finishes. Your working directory is unchanged until you do this.)codex-loop cleanup --apply— remove the worktree and old log artifacts after merging. (Does not delete your code or git history.)
At any time: codex-loop status --summary shows what is happening, and codex-loop events --limit 10 shows the iteration timeline.
All state lives on disk in your project directory, so runs are resumable and inspectable.
Your existing code is safe: Codex works in an isolated Git branch (codex-loop/...), not
directly on your main branch. Nothing changes in your working directory until you explicitly
run git merge. If you don't like the result, just don't merge — your original code is untouched.
$ codex-loop run
Codex working in: /path/to/.codex-loop-worktrees/my-project/codex-loop-001-add-validation-20250610/ (isolated Git branch — your project files are unchanged until you merge)
[iteration 1/30] task: 001-add-validation (0/2 done, running Codex...) [10:15:02]
(waiting for Codex — this can take up to 30 minutes per iteration; run 'codex-loop events --limit 10' in another terminal to watch)
-> status=continue verification=FAIL files_changed=3
verification error (last 300 chars):
FAILED tests/test_forms.py::test_email_validation - AssertionError: expected ValidationError
[iteration 2/30] task: 001-add-validation (0/2 done, running Codex...) [10:31:18]
(waiting for Codex — this can take up to 30 minutes per iteration; run 'codex-loop events --limit 10' in another terminal to watch)
-> status=complete verification=pass files_changed=2
[iteration 3/30] task: 002-write-tests (1/2 done, running Codex...) [10:47:55]
(waiting for Codex — this can take up to 30 minutes per iteration; run 'codex-loop events --limit 10' in another terminal to watch)
-> status=complete verification=pass files_changed=4
All tasks done and verification passed.
completed
Changes are on branch: codex-loop/my-project-a1b2c3
(Your working directory is unchanged until you merge.)
Run these commands from your project directory:
# 1. Review the changes (optional)
git diff --stat main..codex-loop/my-project-a1b2c3
# 2. Merge into your main branch
git checkout main
git merge codex-loop/my-project-a1b2c3
# 3. Clean up worktree and old artifacts
codex-loop cleanup --apply
Ready to start? → Prerequisites → Install → Quick Start.
Want to try a complete worked example first? → examples/simple-local-project — a self-contained todo CLI project you can run without touching your own code.
Time to first run: ~5 minutes if you already have Python 3.11+, Node.js, Codex CLI, and an OpenAI API key. Allow 20–30 minutes if you need to install any of those first (see Prerequisites). Each Codex iteration takes 5–30 minutes; a small task (2–3 iterations) typically completes in under an hour.
Platform: macOS and Linux. Windows is not supported (requires Git worktrees and Unix process management). The service subcommand is macOS-only (launchd); all other commands work on Linux too. Windows users can run codex-loop inside WSL 2 (Windows Subsystem for Linux).
Quick check — run these to confirm you're ready:
python3 --version # need 3.11 or newer
codex --version # need Codex CLI installed
echo $OPENAI_API_KEY # need a non-empty API keyIf any of those fail, see the details below. Otherwise skip straight to Install.
(The Git requirement is checked when you run codex-loop run — it will tell you if anything is missing.)
Before installing codex-loop, make sure you have:
-
Python 3.11+ — check with
python3 --versionIf you have an older version, upgrade via python.org or Homebrew:brew install python@3.11 -
Codex CLI installed and working — install via npm or Homebrew:
npm install -g @openai/codex # requires Node.js 16+; if npm is not found: brew install node # or brew install --cask codex
Verify:
codex --version -
OpenAI API key — get one at platform.openai.com/api-keys, then set it in your shell:
export OPENAI_API_KEY="sk-..."
To make it permanent, add that line to your
~/.zshrcor~/.bashrc. Cost: each iteration calls Codex with a large prompt. Cost depends on the model and codebase size — check current pricing at platform.openai.com/docs/pricing. Set a spending limit on your OpenAI account before running long unattended loops. Model access:codex-loopdefaults togpt-5.4. If your API key does not have access to that model, pass--modeltoinit— it is automatically written intocodex-loop.yaml:codex-loop init --prompt "..." --model o3Common alternatives:
o3,o4-mini. -
A local Git repository with at least one commit — if starting fresh:
git init && git add -A && git commit -m 'init'(After runningcodex-loop init, the.codex-loop/directory is automatically added to.gitignore— do not commit it.) -
Project directory trusted by Codex —
codex execrefuses to run in untrusted directories. You need to trust two paths:- Your project directory (needed for
codex-loop init) - The worktree parent
../.codex-loop-worktrees/(needed forcodex-loop run, which runs Codex inside an isolated Git worktree next to your project)
Easiest setup — run this inside your project directory to add the correct entries (safe to run more than once — skips entries that already exist):
mkdir -p ~/.codex && for p in "$(pwd)" "$(dirname "$(pwd)")/.codex-loop-worktrees"; do grep -qF "[projects.\"$p\"]" ~/.codex/config.toml 2>/dev/null || printf '\n[projects."%s"]\ntrust_level = "trusted"\n' "$p" >> ~/.codex/config.toml; done
Verify both entries were added:
cat ~/.codex/config.tomlThe result should look like:[projects."/Users/alice/code/my-app"] trust_level = "trusted" [projects."/Users/alice/code/.codex-loop-worktrees"] trust_level = "trusted"
- Your project directory (needed for
Platform note: macOS and Linux only. Windows is not supported.
codex-loop has no third-party Python dependencies — just Python 3.11+ and the tools you already have.
Note on the repo name: the repository is called
codex-loop-skill(it also ships a Codex skill), but the installed CLI command iscodex-loop. After installing, usecodex-loop— notcodex-loop-skill.
Where to clone: run these commands from your home directory (or any permanent location — NOT inside your project). The
codex-loop-skilldirectory must stay in place after installing.
cd ~ # or wherever you keep tools — NOT inside your project directory
git clone https://github.com/ly87ing/codex-loop-skill.git
cd codex-loop-skill
python3 -m pip install -e .
cd .. # go back — next, cd into YOUR project directory (not this one)Note:
-e(editable install) means thecodex-loop-skilldirectory must stay where it is — do not move or delete it after installing.
If you get "externally managed environment" error (common on macOS with Homebrew Python): use
pipxinstead (see Tip below), or add--break-system-packagesto the pip command:python3 -m pip install -e . --break-system-packages
Tip: if you use virtual environments and want
codex-loopavailable everywhere without activating one, install withpipxinstead (run this from the parent ofcodex-loop-skill, i.e. aftercd ..):pipx install -e ./codex-loop-skill pipx ensurepath # adds pipx bin dir to PATH; then open a new terminal(Avoids the
command not foundissue without touching your system Python.) Note: the-eflag applies here too — thecodex-loop-skilldirectory must stay in place after installing.
Verify it worked:
codex-loop --help
# If you get 'command not found', see Troubleshooting below.Now go to your own project directory (not codex-loop-skill) to use it:
cd /path/to/your-project # your existing project, or create one: mkdir my-project && cd my-projectTo upgrade later:
cd codex-loop-skill # go into the cloned repo directory
git pull # fetch the latest changes
# No reinstall needed — the -e install picks up changes automatically.
# (If codex-loop --help fails after pulling, re-run: python3 -m pip install -e .)The minimum path to get started. Run these inside your own project directory (not inside the codex-loop-skill repo you just cloned).
Step 0 — One-time setup (skip if already done):
-
Set your OpenAI API key (both
initandrunneed it):export OPENAI_API_KEY="sk-..."
To make it permanent, add that line to your
~/.zshrcor~/.bashrc. -
Make sure your project is a Git repo with at least one commit:
git init && git add -A && git commit -m "init"
-
Trust your project directory and the worktree parent in
~/.codex/config.toml. Run this inside your project directory — safe to run more than once (skips entries that already exist):mkdir -p ~/.codex && for p in "$(pwd)" "$(dirname "$(pwd)")/.codex-loop-worktrees"; do grep -qF "[projects.\"$p\"]" ~/.codex/config.toml 2>/dev/null || printf '\n[projects."%s"]\ntrust_level = "trusted"\n' "$p" >> ~/.codex/config.toml; done
Why two entries? Both
codex-loop initandcodex-loop runcallcodex exec, which refuses to run in untrusted directories. The first entry trusts your project directory (needed forinit). The second trusts the worktree parent (needed forrun, which runs Codex in an isolated Git worktree next to your project). Without both entries you will see "Not inside a trusted directory".Verify both entries were added:
cat ~/.codex/config.tomlPrefer editing manually? Open
~/.codex/config.tomlin any text editor and append these lines (replace the paths with your actual project directory — runpwdto find it):[projects."/Users/alice/code/my-app"] trust_level = "trusted" [projects."/Users/alice/code/.codex-loop-worktrees"] trust_level = "trusted"
Important: if the file already exists, append these entries — do not delete anything already in the file.
Steps 1–6 — Run the loop:
# Move into your own project first
cd /path/to/your-project
# 1. Scaffold workflow files from your goal
# A good prompt names the language/framework, what to build, and how to test it.
# Examples:
# "Add input validation to every form in this app (Python/Flask). Tests in pytest."
# "Implement a REST API endpoint for user registration (Node/Express). Tests in jest."
# "Refactor the database layer to use SQLAlchemy. Tests in pytest."
codex-loop init --prompt "Add input validation to every form in this app (Python/Flask). Tests in pytest."
# If you get a model access error, add: --model o3
# codex-loop init --prompt "..." --model o3
# (--model is automatically written into codex-loop.yaml — no manual edit needed)
# (This calls Codex to generate your project files — usually takes 30–90 seconds.
# The terminal is quiet while Codex works — that silence is normal, not a hang.)
# Success looks like:
# Initialized codex-loop files in /your/project
#
# Next steps:
# 1. Skim the generated files ...
# 2. Confirm verification.commands in codex-loop.yaml ...
# 3. Commit the generated files: git add -A && git commit -m 'add codex-loop files'
# 4. Run: codex-loop run
# (plus trust config reminders for ~/.codex/config.toml)
# 2. Review generated files — especially the verification command
# Open codex-loop.yaml in your editor and confirm verification.commands matches
# how you actually run your tests (this is the most important field):
cat codex-loop.yaml
# Example — if your project uses pytest:
# "verification": { "commands": ["python -m pytest tests/ -q"] }
# Tip: run that command manually now to confirm it works before starting the loop.
# Note: codex-loop.yaml uses JSON syntax (not indented YAML) — that is normal.
# Fields worth knowing: verification.commands (required), execution.max_iterations (iteration cap),
# codex.model (Codex model). Everything else can be left at defaults.
# Do NOT change execution.sandbox or execution.approval — required for unattended runs.
# Also skim spec/, plan/, and tasks/ to make sure the goal was captured correctly.
# If the output looks wrong, re-run with a better prompt:
# codex-loop init --prompt "..." --force
# WARNING: --force deletes all existing state and run history. Only use it before you run
# 'codex-loop run' for the first time, or when you intentionally want to start over.
# 3. Commit the generated files so Codex can see them in its isolated worktree
# (codex-loop run creates a Git worktree from your latest commit — files not
# committed yet are invisible to Codex)
# If git commit fails with 'Author identity unknown', set your Git identity first:
# git config --global user.email "you@example.com"
# git config --global user.name "Your Name"
git add -A && git commit -m "add codex-loop files"
# 4. Confirm ~/.codex/config.toml has both trust entries
# (codex-loop run runs Codex in a worktree next to your project — both paths must be trusted)
# If you already did Step 0 above, both entries are already in place — just verify:
# cat ~/.codex/config.toml
# Otherwise, run this inside your project directory to add the correct entries (safe to run more than once):
mkdir -p ~/.codex && for p in "$(pwd)" "$(dirname "$(pwd)")/.codex-loop-worktrees"; do grep -qF "[projects.\"$p\"]" ~/.codex/config.toml 2>/dev/null || printf '\n[projects."%s"]\ntrust_level = "trusted"\n' "$p" >> ~/.codex/config.toml; done
# Then verify:
# cat ~/.codex/config.toml
# 5. Run the loop — it will keep working until done or genuinely blocked
# Note: Codex runs in an isolated Git worktree containing only committed files.
# If your tests need untracked files (e.g. .env, node_modules), add a hook:
# "hooks": { "pre_iteration": ["cp /path/to/.env $CODEX_LOOP_WORKING_DIR/"] }
# See 'Known Limits' in this README for details.
# Each iteration calls Codex and waits up to 30 minutes for a response.
# A typical small task takes 3–10 iterations (15 minutes to a few hours).
# Each iteration shows one progress line before Codex starts, then goes quiet
# while Codex works (up to 30 min per iteration) — silence during that gap is normal.
# A result line is printed after each iteration completes.
# To see a human-readable timeline at any time, open another terminal and run:
# codex-loop events --limit 10
# (For raw Codex output: codex-loop logs tail --lines 50)
# You can press Ctrl-C at any time to stop safely; the next run picks up where it left off.
# If the loop stops with 'blocked', see 'If the loop blocks' section below for common causes and fixes.
codex-loop run
# You will see output like:
# Codex working in: /path/to/.codex-loop-worktrees/my-project/codex-loop-.../ (isolated Git branch — your files unchanged until you merge)
# [iteration 1/30] task: 001-foundation (0/2 done, running Codex...) [14:23:01]
# (waiting for Codex — this can take up to 30 minutes per iteration; run 'codex-loop events --limit 10' in another terminal to watch)
# -> status=continue verification=FAIL files_changed=3
# ("continue" = Codex is still working on it; "FAIL" = tests not passing yet — this is normal)
# verification error (last 300 chars):
# FAILED tests/test_todo.py::test_add - AssertionError: expected 1 item, got 0
# [iteration 2/30] task: 001-foundation (0/2 done, running Codex...) [14:31:45]
# (waiting for Codex — this can take up to 30 minutes per iteration; run 'codex-loop events --limit 10' in another terminal to watch)
# -> status=complete verification=FAIL files_changed=2
# ("complete" = Codex thinks it's done, but tests still fail — loop keeps going automatically)
# [iteration 3/30] task: 001-foundation (0/2 done, running Codex...) [14:39:12]
# (waiting for Codex — this can take up to 30 minutes per iteration; run 'codex-loop events --limit 10' in another terminal to watch)
# -> status=complete verification=pass files_changed=3
# ("complete" + "pass" = task verified done; loop moves to next task)
# All tasks done and verification passed.
# completed
# Changes are on branch: codex-loop/my-project-abc123
# (Your working directory is unchanged until you merge.)
#
# Run these commands from your project directory:
#
# # 1. Review the changes (optional)
# git diff --stat main..codex-loop/my-project-abc123
#
# # 2. Merge into your main branch
# git checkout main
# git merge codex-loop/my-project-abc123
#
# # 3. Clean up worktree and old artifacts
# codex-loop cleanup --apply
# Note: Codex writes all changes to an isolated Git branch, not your project directory.
# You won't see any changed files locally until you merge in step 6.
# 6. Merge the changes
# When the loop completes, it prints the exact commands to run. Copy and run them:
# git checkout main # or master, or your default branch
# git merge codex-loop/<branch> # use the branch name printed above
# If you need to find the branch name later:
# git branch | grep codex-loop
# After merging, clean up old log/run artifacts (does NOT delete your code or git history):
codex-loop cleanup --apply
# 7. Check status at any time
codex-loop status --summary
# If something looks wrong: codex-loop doctor (checks config, tasks, and state)Example output (while running):
project: my-project
overall_status: running # running | completed | blocked
iteration: 3
no_progress_iterations: 0 # iterations with no file changes (resets on progress)
tasks:
[x] 001-create-schema (done)
[~] 002-add-api (in_progress)
[ ] 003-write-tests (ready)
current_task: 002-add-api
runner_failures_total: 0 # total codex exec failures so far
verification_failures_total: 2 # total failed verification runs so far
worktree_branch: codex-loop/my-project-abc123
When the run finishes successfully (overall_status: completed), codex-loop status --summary also prints the merge commands:
project: my-project
overall_status: completed
...
worktree_branch: codex-loop/my-project-abc123
Next steps (all tasks done):
# 1. Review the changes (optional)
git diff --stat main..codex-loop/my-project-abc123
# 2. Merge into your main branch
git checkout main
git merge codex-loop/my-project-abc123
# 3. Clean up worktree and old artifacts
codex-loop cleanup --apply
Task markers: [x] done, [~] in progress, [!] blocked, [ ] pending.
If overall_status is blocked, the loop stopped — run codex-loop run --retry-blocked to retry.
That is all you need for most tasks. The loop stops by itself when all tasks pass verification, or when it hits a real blocker (no progress, too many failures). If something goes wrong, see the Troubleshooting section below for common errors and fixes.
See examples/simple-local-project/ for a worked example
showing what codex-loop init generates for a real goal (a Python todo CLI), with realistic
spec, plan, tasks, and config files you can copy as a starting point.
That directory includes step-by-step instructions in its README — it is the fastest way to see a complete end-to-end run without setting up your own project first.
The loop runs Codex in an isolated Git branch (prefix: codex-loop/). When it finishes,
codex-loop run prints the exact merge command to use:
completed
Changes are on branch: codex-loop/my-project-abc123
(Your working directory is unchanged until you merge.)
Run these commands from your project directory (/path/to/your-project):
# 1. Review the changes (optional)
git diff --stat main..codex-loop/my-project-abc123
# 2. Merge into your main branch
git checkout main
git merge codex-loop/my-project-abc123
# 3. Clean up worktree and old artifacts
codex-loop cleanup --apply
The exact branch name is printed by the loop — copy it from your terminal output. Or inspect the changes first:
# Make sure you're on your main branch
git checkout main # or master, or whatever your default branch is
# See which files were changed
git diff --stat main..codex-loop/my-project-abc123
# See the full diff
git diff main..codex-loop/my-project-abc123
# See the commit history
git log main..codex-loop/my-project-abc123 --oneline
# Then merge
git merge codex-loop/my-project-abc123If you need to find the branch name later:
git branch | grep codex-loop
# or
codex-loop status --summary # shows worktree_branchThe changes live in an isolated Git branch (codex-loop/...). Use git diff and git log (shown above) to review — you don't need to navigate to any worktree directory. After merging, clean up with:
codex-loop cleanup --apply
# This removes the worktree directory and prunes old logs under .codex-loop/.
# It does NOT touch your source code, your main branch, or the merged changes.
# The codex-loop/... Git branch is NOT deleted — to remove it too:
# git branch -d codex-loop/<branch-name>If you inspect the changes and decide you don't want them, simply don't merge. The branch and worktree stay in place until you clean them up. To discard everything and start fresh:
codex-loop cleanup --apply # removes worktrees and old artifacts
codex-loop init --prompt "..." --force # re-scaffold (WARNING: resets all state)After merging and cleaning up, you can start a new loop on the same project:
codex-loop cleanup --apply # clean up old worktrees and artifacts first
codex-loop init --prompt "your next goal" --force # re-scaffold with new goal
codex-loop run--force is required because codex-loop.yaml and the task files already exist. It will replace them with a fresh scaffold for the new goal. Your source code and git history are not affected — only the codex-loop workflow files (codex-loop.yaml, spec/, plan/, tasks/, .codex-loop/) are overwritten.
When the loop stops with blocked, the reason is printed directly in the terminal:
blocked
Blocked: [no_progress_limit] No file changes detected for 4 consecutive iterations.
Run 'codex-loop status --summary' for full details.
To retry: codex-loop run --retry-blocked
For more detail:
codex-loop doctor # checks config, tasks, and state — prints specific hints
codex-loop status --summary # current task, blocker code, blocker reason
codex-loop events --limit 20 # full event timeline with verification outputAfter fixing the root cause, retry:
# Retry once
codex-loop run --retry-blocked
# Or keep retrying automatically
codex-loop run --continuous --retry-blocked --cycle-sleep-seconds 60If retrying still blocks, common fixes:
no_progress_limit(no file changes): the task description may be too vague or too large. Edit the task file intasks/to be more specific or split it into smaller steps, commit the change (git add -A && git commit -m 'refine task'), then:codex-loop run --retry-blocked.runner_failure_circuit_breaker(Codex exec keeps failing): check your API key, network, and Codex version (codex --version). Update withnpm install -g @openai/codex.verification_failure_circuit_breaker(tests always fail): the verification command may be wrong — checkverification.commandsincodex-loop.yaml. Run it manually to confirm it works. Seecodex-loop events --limit 20for the test output.- Increase the iteration budget: edit
execution.max_iterationsincodex-loop.yaml(default: 30) if the task is large or the tests are flaky. A rule of thumb: allow roughly 5–10 iterations per task file.
Use daemon (or service on macOS) when you want the loop to run in the background and survive terminal closes. Use run --continuous if you want to keep the terminal attached and watch the output.
# Run as a background daemon with auto-restart
codex-loop daemon start --retry-blocked --cycle-sleep-seconds 60
codex-loop daemon status --json
codex-loop daemon stop
# Or install as a macOS launchd service that survives reboots
codex-loop service install --retry-blocked --cycle-sleep-seconds 60
codex-loop service status --json
codex-loop service uninstallcodex-loop health
codex-loop logs tail --lines 20
codex-loop cleanup --keep 10 # dry-run preview
codex-loop cleanup --apply --keep 10 --older-than-days 14For most tasks, you only need three commands: init, run, and status --summary.
The rest are for inspection, long-running unattended jobs, or cleanup.
| Command | What it does |
|---|---|
init --prompt "..." |
Scaffold spec, plan, tasks, and config from your goal. Add --model <name> to override the Codex model used for init (default: gpt-5.4). |
run |
Run the loop until done or blocked (exits 0 on success, 2 if blocked) |
run --retry-blocked |
Requeue blocked tasks then run |
run --continuous --retry-blocked |
Keep retrying after blocks until --max-cycles |
doctor --repair |
Diagnose and repair state drift — run does this automatically on startup, but running it explicitly surfaces errors earlier |
health |
One-command overview: status, warnings, events, daemon state |
status --summary |
Show current task status and loop health |
events --limit 20 |
Show the recent event timeline |
logs tail --lines 20 |
Tail the loop log |
cleanup --keep 10 |
Preview artifact pruning (dry-run) |
cleanup --apply --keep 10 |
Actually prune old artifacts |
daemon start |
Run as a background watchdog process |
daemon status |
Check watchdog health |
daemon stop |
Stop the background watchdog |
service install |
Install as a macOS launchd service (survives reboots) |
service status |
Check service health |
service uninstall |
Remove the launchd service |
Codex handles individual prompts well, but longer tasks need:
- durable state across iterations (a single prompt drifts and forgets)
- verification gates (the loop only calls something done when tests actually pass)
- unattended execution (no interactive approval prompts mid-run)
- a structured way to see what happened when something went wrong
your-project/
codex-loop.yaml # commit this — runtime config (verification commands, model, limits)
spec/ # commit these — human-readable project docs (generated by init)
001-project-spec.md # what the project does and when it is "done"
plan/
001-implementation-plan.md # step-by-step breakdown (for reference; Codex uses tasks/)
tasks/ # commit these — Codex reads these as its work queue
001-*.md # one file per task; edit these to refine or split work
.codex-loop/ # do NOT commit — codex-loop init automatically adds this
# to both .gitignore and .git/info/exclude
state.json # loop state (task status, history, blockers)
metrics.json # counters and blocker aggregates
agent_result.schema.json
logs/ # per-iteration Codex output (one JSON object per line)
runs/ # per-task last result JSON
artifacts/ # snapshots and exports
# Codex runs in an isolated Git worktree — managed automatically, no need to navigate there
Each file in tasks/ is a Markdown document. The filename determines execution order (001-, 002-, ...). The content is passed directly to Codex as the task description.
To declare that a task depends on another:
<!-- depends_on: 001-foundation -->The loop skips a task until all its dependencies are done. codex-loop init generates these automatically from your prompt.
You can edit task files freely before or between runs to tighten the description, split a task, or remove tasks you don't need. To add a new task, create a new Markdown file with the next number in the sequence (e.g. tasks/003-my-new-task.md) — the loop will pick it up automatically. After editing or adding tasks:
- Commit the changes:
git add tasks/ && git commit -m 'update task'(Codex runs in an isolated Git worktree built from your latest commit — uncommitted edits are invisible to it.) - Run
codex-loop run --retry-blockedto resume. (codex-loop runautomatically runsdoctor --repairon startup to sync the state file — no need to run it manually.)
What makes a good task description? The content is passed verbatim to Codex, so the more specific it is, the better the result:
# Too vague (Codex may not know where to start)
Add validation to the app.
# Good (names the files, the rules, and the test to pass)
Add input validation to the registration form in `src/routes/auth.py`:
- email must match RFC 5322 format
- password must be at least 8 characters
Raise `ValueError` with a descriptive message on invalid input.
Tests in `tests/test_auth.py` must pass with `python -m pytest tests/test_auth.py -q`.If codex-loop init generates tasks that are too vague, edit them before running, or re-run init with a more specific prompt.
Hooks let you run shell commands at key points in the loop. Configure them in codex-loop.yaml:
"hooks": {
"pre_iteration": ["cp .env $CODEX_LOOP_WORKING_DIR/"],
"post_iteration": [],
"on_completed": ["bash scripts/notify.sh"],
"on_blocked": []
}Available events:
| Event | When it runs |
|---|---|
pre_iteration |
Before each Codex call |
post_iteration |
After verification completes |
on_completed |
When the loop finishes successfully |
on_blocked |
When the loop stops due to a blocker |
Environment variables available in every hook:
| Variable | Value |
|---|---|
CODEX_LOOP_PROJECT_DIR |
Absolute path to your project directory |
CODEX_LOOP_WORKING_DIR |
Absolute path to the worktree (where Codex is working) |
CODEX_LOOP_TASK_ID |
Current task ID (e.g. 001-foundation) |
CODEX_LOOP_TASK_TITLE |
Current task title |
CODEX_LOOP_EVENT |
Event name (pre_iteration, on_completed, etc.) |
Hooks run in the worktree directory. If a hook exits non-zero and hooks.failure_policy is "block" (default: "ignore"), the loop stops.
- Load
codex-loop.yamland auto-repair any state drift - Pick the next pending task from
tasks/(in filename order) - Create or reuse an isolated Git worktree so your main branch stays clean
- Run
codex execon that task - Run every command in
verification.commands - If verification passes and the task is done, move to the next task
- If something fails, update failure counters and retry or block based on thresholds
- Record everything to
.codex-loop/for later inspection - Repeat until all tasks are done or a blocking threshold is reached
The loop is safe to interrupt with Ctrl-C at any time. State is written after each completed iteration, so a subsequent codex-loop run picks up where it left off.
For longer unattended runs, add --continuous --retry-blocked: when a cycle blocks, it requeues blocked tasks and starts the next cycle until completion or --max-cycles.
For background execution, use daemon start (watchdog process) or service install (macOS launchd, survives reboots). See the Command Reference table above.
The loop only stops with success when:
- every task is
done - all verification commands pass
The loop stops with blocked when:
- Codex itself reports it is blocked
- Too many consecutive
codex execfailures (configurable limit — stops the loop from spinning on a broken setup) - Too many consecutive failed verification runs, if enabled (
max_consecutive_verification_failuresincodex-loop.yaml; default0= disabled, loop keeps retrying) - Total iteration limit is reached
- No file changes detected across too many iterations (no-progress limit)
doctorfinds unrecoverable local state or task file problems
All thresholds are configurable in codex-loop.yaml under execution.
sandbox_mode="workspace-write"— Codex can read and write files in your project, but cannot access the network or run arbitrary system commands outside the workspaceapproval_policy="never"— Codex does not pause for interactive approval, which is required for unattended runs; changes go to an isolated Git branch so you can review before merging- The supervisor keeps
.codex-loop/local state outside normal task files - The supervisor can repair a missing schema and task/state drift before entering the loop
- Hook execution is local and explicit through
codex-loop.yaml; never inferred from prompts - The default finish mode is conservative: keep the worktree and branch after completion
healthgives the fastest single-command overview: it combines status, doctor warnings, event signals, and daemon/service state into one report.status --summaryshows the current task, blocker code, and blocker reason when the loop stops.doctor --repairbackfills missing config defaults and reconciles task/state drift.codex-loop rundoes this automatically on startup — run it explicitly only if you want to surface issues before starting the loop.cleanupdefaults to dry-run (preview only). Use--applyonly after reviewing what would be deleted.daemonandserviceare mutually exclusive for the same project root — pick one or the other for background runs.
Something not working? Start here:
codex-loop doctor # checks config, task files, and state — prints specific hints
codex-loop status --summary # shows current task, last blocker, iteration count
codex-loop events --limit 10 # shows what happened in recent iterationsThen look up your specific error below.
The codex-loop command is installed by pip into a user scripts directory that may not be on your PATH.
Fix with pipx (recommended — run from the parent directory of codex-loop-skill):
pipx install -e ./codex-loop-skill
pipx ensurepath # adds pipx bin dir to PATH if not already there
# Then open a new terminal window (or run: source ~/.zshrc)Or find where pip installed the script and add that to your PATH:
python3 -c "import sysconfig; print(sysconfig.get_path('scripts'))"
# Copy the output (e.g. /Users/you/Library/Python/3.11/bin) and add to ~/.zshrc or ~/.bashrc:
# export PATH="/Users/you/Library/Python/3.11/bin:$PATH"Or run it directly without installing (run this from inside the codex-loop-skill directory):
cd ~/codex-loop-skill # or wherever you cloned it
PYTHONPATH=src python3 -m codex_loop.cli --help
# Use PYTHONPATH=src python3 -m codex_loop.cli <command> for all commandsOn macOS with Homebrew Python (or some Linux distros), pip refuses to install globally.
The recommended fix is pipx (run from the parent directory of codex-loop-skill):
pipx install -e ./codex-loop-skill
pipx ensurepath
# Then open a new terminalOr override the restriction (fine for a personal tool install):
python3 -m pip install -e . --break-system-packagesBoth codex-loop init and codex-loop run call codex exec, which exits immediately if
OPENAI_API_KEY is not set or is invalid. Get a key at platform.openai.com/api-keys.
export OPENAI_API_KEY="sk-..."To make it permanent, add that line to your ~/.zshrc or ~/.bashrc.
codex-loop defaults to gpt-5.4. Not all API keys have access to this model. If you see an error like
model not found, invalid model, or you do not have access to this model, use a different model:
# Re-run init with a supported model (--model is written into codex-loop.yaml automatically)
codex-loop init --prompt "..." --model o3
# or
codex-loop init --prompt "..." --model o4-miniIf you already ran init and just need to change the model, edit codex-loop.yaml directly:
"codex": { "model": "o3" }Then verify the file is valid JSON: python3 -m json.tool codex-loop.yaml
Both codex-loop init and codex-loop run call codex exec, which refuses to run in
directories that Codex has not explicitly trusted.
Important: codex-loop run runs Codex inside an isolated Git worktree at a path like
../.codex-loop-worktrees/<project>/<branch>/ — a different directory from your project.
Trusting only your project directory is not enough; you need to trust the worktree parent too.
The error message will print the exact path that needs to be trusted.
For codex-loop init failures, the missing path is your project directory.
For codex-loop run failures, it is usually the worktree parent.
The quickest fix — run this inside your project directory (safe to run more than once — skips entries that already exist):
mkdir -p ~/.codex && for p in "$(pwd)" "$(dirname "$(pwd)")/.codex-loop-worktrees"; do grep -qF "[projects.\"$p\"]" ~/.codex/config.toml 2>/dev/null || printf '\n[projects."%s"]\ntrust_level = "trusted"\n' "$p" >> ~/.codex/config.toml; doneVerify both entries are present:
cat ~/.codex/config.tomlAlternatively, run codex once interactively inside each directory to trigger the interactive
trust prompt (but you would need to do that after the worktree is created, so the config.toml
approach above is easier).
codex-loop run requires a Git repository with at least one commit.
If you see this error, initialize the repository first:
git init
git add -A
git commit -m "init"(.codex-loop/ is automatically added to .gitignore by codex-loop init, so git add -A is safe.)
This means a previous run left a lock file behind (usually because it was force-killed or crashed).
The error message prints the exact kill and rm commands to unblock:
# If the listed PID is no longer running, delete the lock file directly:
rm .codex-loop/run.lock
# Then re-run:
codex-loop runIf the PID is still alive (a run genuinely in progress), wait for it to finish or stop it first:
kill <pid> # use the PID shown in the error messageThis is not a crash — it means the loop hit a real limit it could not resolve on its own. The reason is printed directly in the terminal when the loop exits. For more detail:
codex-loop status --summary # task status and blocker reason
codex-loop events --limit 20 # full timeline with verification outputCommon causes and fixes:
| Blocker | What happened | What to do |
|---|---|---|
no_progress_limit |
Codex made no file changes for 5 consecutive iterations (default) | Review the task description; make it more specific, then git add -A && git commit -m 'refine task' and codex-loop run --retry-blocked |
runner_failure_circuit_breaker |
codex exec failed repeatedly |
Check your API key and network; run codex manually to verify |
verification_failure_circuit_breaker |
Tests kept failing | Look at codex-loop events --limit 20 for the error output |
task_failure_circuit_breaker |
One task failed too many times; loop continues with next task | Check codex-loop status --summary to see which task was skipped |
max_iterations |
Hit the iteration cap | Increase max_iterations in codex-loop.yaml or break the task into smaller pieces |
agent_blocked |
Codex reported it is stuck and cannot continue | Edit the relevant task file to give more context, git add -A && git commit -m 'refine task', then codex-loop run --retry-blocked |
no_selectable_task |
No task can be selected — likely a dependency cycle or all remaining tasks are blocked | Run codex-loop status --summary to see which tasks are blocked; if a dependency is blocked, edit that task file and codex-loop run --retry-blocked; if it is a dependency cycle, edit tasks/ to remove the cycle, commit, then retry |
After fixing the root cause:
codex-loop run --retry-blockedThe loop injects the last failed verification output into the next prompt automatically. If it keeps failing, the test command itself may be wrong.
Note: verification commands run inside the worktree (the isolated Git branch), not your project directory.
To reproduce what the loop sees, run from the worktree path printed by codex-loop run:
# Find the worktree path
codex-loop status --summary # shows worktree_branch
git worktree list # shows all worktree paths
# Run your verification command from the worktree directory
cd /path/to/.codex-loop-worktrees/my-project/codex-loop-...
python -m pytest tests/ -qOr run it from your project directory to check the command syntax is correct (results may differ if files are not yet committed):
python -m pytest tests/ -qTo fix the test command, edit verification.commands in codex-loop.yaml:
"verification": {
"commands": [
"python -m pytest tests/ -q"
]
}To run multiple commands (all must pass):
"verification": {
"commands": [
"python -m pytest tests/ -q",
"python -m mypy src/"
]
}Common examples:
- Python pytest:
python -m pytest tests/ -q - Node.js:
npm test - Go:
go test ./... - Custom script:
bash scripts/verify.sh
If your project uses a virtual environment, use the full path to the interpreter so the command works without activating it first:
".venv/bin/python -m pytest tests/ -q"(Linux/macOS with venv in.venv/)"./node_modules/.bin/jest"(Node.js with local install)
After editing, verify the file is valid JSON (python3 -m json.tool codex-loop.yaml), then: codex-loop run --retry-blocked.
If your project has no tests yet: set verification.commands to an empty list in codex-loop.yaml:
"verification": { "commands": [] }The loop will run until Codex declares all tasks done, with no pass/fail gate. This is fine for getting started, but without verification the loop cannot tell if the code actually works — add real tests when you can.
If codex-loop init produced tasks that don't match your goal, re-run it with a better prompt:
codex-loop init --prompt "your more specific goal" --force--force overwrites the previous generated files. Tips for a better prompt:
- Name the language and framework explicitly (e.g. "Python/Flask", "Node/Express")
- Name the test tool (e.g. "tests in pytest", "tests in jest")
- Be specific about what to build (e.g. "add input validation to every form" not just "add validation")
codex-loop.yaml uses JSON syntax (curly braces, quoted keys, commas) even though it has a .yaml extension. If you accidentally introduced invalid JSON:
# Check the syntax:
python3 -m json.tool codex-loop.yamlThe error message will show the line number. Common mistakes: trailing commas, missing quotes around keys, single quotes instead of double quotes, comments (JSON does not support // or # comments). If the file is beyond repair, re-generate it with:
codex-loop init --prompt "your goal" --forceWhile the loop runs, Codex output is captured internally (not shown live) so the supervisor can parse the JSON result.
For a human-readable summary of what happened in recent iterations (recommended):
codex-loop events --limit 10Example output:
2025-06-10T14:23:01+00:00 iteration:continue task=001-foundation iteration 1: wrote Storage class skeleton
2025-06-10T14:39:45+00:00 iteration:continue task=001-foundation iteration 2: added argparse subcommands, tests still failing
2025-06-10T14:55:03+00:00 iteration:complete task=001-foundation iteration 3: all tests passing
2025-06-10T15:10:22+00:00 iteration:complete task=002-core-commands iteration 4: implemented add/list/done/delete
To see the raw Codex output (one JSON object per line — useful for debugging):
codex-loop logs tail --lines 50After editing, commit the changes so Codex can see them (it runs in an isolated Git worktree built from your latest commit — uncommitted edits are invisible to it):
git add -A && git commit -m 'update task'
codex-loop run --retry-blocked # doctor --repair runs automatically on startupIf Codex edited files that you also modified since the run started, Git may report merge conflicts. Resolve them as you normally would:
# After git merge codex-loop/<branch> reports conflicts:
git status # see which files have conflicts
# Edit the conflicting files to resolve the <<< === >>> markers
git add <resolved-files>
git commit # complete the mergeAlternatively, inspect the changes first and decide what to keep:
git diff main..codex-loop/<branch> # see exactly what Codex changedIf the conflicts are too complex and you want to start fresh, discard the merge and the branch:
git merge --abort # cancel the in-progress merge
codex-loop cleanup --apply # remove worktrees and old artifactsCodex makes changes in an isolated Git branch, not in your project directory directly. Your project files are unchanged until you merge. That is why running tests locally shows the old code.
After the loop completes, merge the branch first:
git checkout main # or master
git merge codex-loop/<branch> # use the branch name printed by codex-loop runThen run your tests. They should pass against the merged code.
- Untracked files are not in the worktree. Codex runs in an isolated Git worktree that only contains committed files. If your project needs untracked files to run tests, add a
pre_iterationhook incodex-loop.yamlthat sets them up before each iteration:Common cases:"hooks": { "pre_iteration": [ "cp /path/to/your-project/.env $CODEX_LOOP_WORKING_DIR/", "npm install --prefix $CODEX_LOOP_WORKING_DIR" ] }
.envfiles (copy them in),node_modules/(runnpm installrather than copying — it's faster), Python virtualenvs (runpip install -e .orpip install -r requirements.txtin the worktree). The worktree path is also available as$CODEX_LOOP_WORKING_DIRin hooks. Alternatively, use the full interpreter path inverification.commands(e.g.".venv/bin/python -m pytest tests/ -q") — see Verification keeps failing. - Each iteration waits up to 30 minutes for Codex to respond (
iteration_timeout_seconds: 1800incodex-loop.yaml). A progress line is printed when each iteration starts; the terminal then goes quiet while Codex works — that silence is normal. A result line is printed when the iteration completes. Runcodex-loop events --limit 10to see a summary of recent iterations, or reduceiteration_timeout_secondsincodex-loop.yamlif you need a shorter timeout. codex-loop.yamluses JSON syntax (curly braces and quoted keys), not indented YAML. This is intentional — it avoids a PyYAML dependency. Edit it with any text editor; just keep the JSON structure intact. If you break it accidentally, check with:python3 -m json.tool codex-loop.yaml- Codex CLI approval behavior can vary by CLI version. This project passes
approval_policy="never"tocodex exec, but some Codex releases have edge cases where Codex still pauses for interactive input. If an iteration hangs for more than 30 minutes (the defaultiteration_timeout_seconds), it will time out and be counted as a runner failure. If you see repeated runner failures with no clear error, update Codex CLI:npm install -g @openai/codex(orbrew upgrade --cask codex). - Some Codex resume failures are only detectable from CLI error text, so session fallback is heuristic rather than protocol-level.
- Task execution is sequential in the first version. There is no parallel task scheduler yet.
This repository also ships a Codex skill at skills/codex-loop/SKILL.md. The skill teaches Codex when and how to use codex-loop — so instead of running CLI commands yourself, you can just describe your goal to Codex and it will call codex-loop init, review the output, and start the loop for you.
To use it, run these commands from the directory where you cloned codex-loop-skill:
mkdir -p ~/.codex/skills
ln -s "$(pwd)/skills/codex-loop" ~/.codex/skills/codex-loop(Run pwd first if you're unsure of the path — the symlink must point to the actual location on disk.)
(This creates a symlink so Codex always uses the latest version when you git pull.)
Codex will pick it up automatically on the next run.
To verify it was installed:
ls ~/.codex/skills/codex-loop
# Should show: SKILL.md references/Run tests:
PYTHONPATH=src python3 -m unittest discover -s testsCompile check:
python3 -m compileall srcIf something is not working:
- Run
codex-loop doctor— it checks your config, task files, and state file, and prints specific hints for common problems. - Run
codex-loop status --summary— it shows which task is active, what the last blocker was, and how many iterations have run. - If still stuck, open an issue at https://github.com/ly87ing/codex-loop-skill/issues with:
- the command you ran
- the error message or unexpected output
- your OS,
codex --version, andcodex-loop --version