cheat sheet

codex exec

Complete reference for codex exec — scripted invocation, stdin piping, JSON event streams, output formats, exit codes, timeouts, and automation patterns for CI, git hooks, and shell pipelines.

codex exec — Non-Interactive Mode

What it is

codex exec is Codex CLI's headless one-shot mode. It accepts a prompt (as an argument, from stdin, or both), runs the agent loop to completion, streams output to stdout, and exits with a status code reflecting success or failure. It does NOT open the TUI, does NOT prompt the user unless --ask-for-approval is enabled, and does NOT consume the interactive history file unless --save is passed. It is the primary surface for CI pipelines, git hooks, editor save-actions, shell aliases, and anything else that needs a deterministic exit.


Invocation syntax

bash
codex exec [OPTIONS] [PROMPT]

Output: (none — syntax reference only)

The PROMPT argument is optional — if omitted, Codex reads the prompt from stdin. If both are present, stdin content is appended to the prompt as additional context.

bash
codex exec "What does this repo do?"

Output:

text
This repository is a personal cheat-sheet site built with Astro 4 and Tailwind CSS. …

Reading prompts from stdin

The most useful exec pattern: pipe a file, a diff, a log, or any other long context into Codex without ballooning the command line.

bash
git diff main...HEAD | codex exec --output-last-message "Summarise this diff for a PR description"

Output:

text
This PR refactors the authentication module to use JWT tokens, adds rate limiting middleware, and updates the test suite to cover token rotation.

Pipe a file with an explicit prompt:

bash
cat src/auth.py | codex exec "Review this file for security issues; output bullets only"

Output:

text
- Token compared with == instead of secrets.compare_digest (line 42)
- SQL query constructed via f-string, not parameterised (line 78)
- No rate limit on login endpoint

Empty prompt argument, full stdin:

bash
echo "Write a haiku about TOML" | codex exec

Output:

text
Tables nest like pines
Keys whisper to silent vals
Brackets hold the dawn

Output formats

The --output-format flag controls how Codex streams the agent's progress and final answer. Choose text for human reading, last-message for shell pipelines, and json / stream-json for programmatic consumers.

FormatFlagDescription
Text--output-format text (default)Streamed plain text with diff previews
Last message--output-last-messageOnly the final assistant message, plain text
JSON--output-format jsonSingle JSON object printed after completion
Stream JSON--output-format stream-jsonNewline-delimited JSON events as they arrive
NDJSON events--json (alias for stream-json)Same as stream-json

Text output

The default — useful when a human is watching the terminal.

bash
codex exec "Add a docstring to every public function in src/utils.py"

Output:

text
[agent] Reading src/utils.py…
[agent] Adding docstrings to 4 functions.
[diff] src/utils.py: +24 lines
[agent] Done. 4 docstrings added.

Last-message output

Strips all intermediate output and prints only the final assistant message. Ideal for $(...) capture.

bash
title=$(codex exec --output-last-message "Suggest a 6-word PR title for the staged diff" < <(git diff --cached))
echo "$title"

Output:

text
Refactor auth module to use JWT

JSON output

A single JSON object with full session metadata, printed atomically after the agent finishes.

bash
codex exec --output-format json "List three Linux process-inspection tools as JSON"

Output:

text
{"type":"result","subtype":"success","session_id":"th_01abc","turn_count":1,"last_message":"[\"ps\",\"top\",\"lsof\"]","exit_code":0}

Parse with jq:

bash
codex exec --output-format json "Run pytest and report the count" | jq -r '.last_message'

Output:

text
142 tests passed, 0 failed.

Stream-JSON / NDJSON

One JSON object per line, emitted as events occur. The richest format for programmatic consumers — you see every tool call, every result, and the final turn-complete event.

bash
codex exec --json "Run the test suite and summarise failures"

Output:

text
{"type":"session-start","session_id":"th_01abc","model":"gpt-5","cwd":"/home/alice/myproject"}
{"type":"agent-turn-start","turn_id":"t_01"}
{"type":"tool-call","tool":"shell","input":{"cmd":"pytest"}}
{"type":"tool-result","tool":"shell","output":"142 passed, 3 failed"}
{"type":"agent-turn-complete","turn_id":"t_01","last_message":"3 tests failed: test_auth, test_db_pool, test_jwt_refresh."}
{"type":"session-end","session_id":"th_01abc","exit_code":0}

Filter to just final messages with jq:

bash
codex exec --json "What does this repo do?" | jq -r 'select(.type=="agent-turn-complete") | .last_message'

Output:

text
This repository is a personal cheat-sheet site …

Exit codes

codex exec returns a meaningful exit code that you can use in shell pipelines. The most common codes:

CodeMeaning
0Success — agent completed and produced a final message
1Generic error — model error, network error, or unrecognised failure
2Approval denied — user (or hook) refused a required tool call
3Sandbox violation — agent attempted a denied operation and could not recover
4Authentication error — invalid or missing OPENAI_API_KEY / expired token
5Configuration error — config.toml failed to parse
6Timeout — --timeout exceeded
7Max-turns exceeded — --max-turns N reached before the agent finished
130Cancelled — user pressed Ctrl+C (SIGINT)

Use exit codes in pipelines:

bash
codex exec --full-auto "Fix all ruff issues" || echo "Codex run failed with code $?" >&2

Output:

text
[agent fixes issues; exits 0 on success]

Fail-fast in a chain:

bash
codex exec -p ci "Bump version in pyproject.toml" \
  && codex exec -p ci "Write CHANGELOG entry for the bump" \
  && git commit -am "chore: bump version"

Output:

text
[two agent runs followed by a commit; aborts on first failure]

Common flags

The flags most often needed in exec mode. The full list also includes everything from interactive mode (model, profile, sandbox, etc.).

FlagDescription
--output-format <fmt>text / json / stream-json
--output-last-messageEquivalent to --output-format last-message
--jsonAlias for --output-format stream-json
--max-turns <n>Cap agent loop at N turns (default unlimited)
--timeout <secs>Wall-clock timeout for the whole run
--ephemeralDon't write the session to history
--savePersist the session even though it's non-interactive
--skip-git-repo-checkAllow running outside a git repo
--full-autoShorthand for --sandbox workspace-write --ask-for-approval on-request
--ask-for-approval <policy>Override approval policy (default never in exec)
--sandbox <mode>Override sandbox mode
--profile <name>Activate a named profile
--cd <dir>Run as if cwd were <dir>

Timeouts and turn caps

In exec mode the agent can loop indefinitely if no terminating condition is reached. Two guard-rails prevent runaway runs: wall-clock timeout and max-turns.

Wall-clock timeout

bash
codex exec --timeout 120 "Fix any failing tests"

Output:

text
[agent runs for up to 120 seconds; exits with code 6 if not done in time]

Max-turns cap

bash
codex exec --max-turns 8 "Fix any failing tests"

Output:

text
[agent runs for at most 8 tool-use cycles; exits with code 7 if it hits the cap]

Combine both

bash
codex exec --timeout 300 --max-turns 20 "Refactor the auth module"

Output:

text
[bounded by both wall-clock and turn count, whichever trips first]

Ephemeral vs. persisted sessions

By default, codex exec does NOT save to ~/.codex/history/ — it's "fire and forget." Pass --save if you want to resume the session later. Pass --ephemeral to be explicit about throwaway use.

bash
codex exec --save "Refactor the DB layer"

Output:

text
[agent output]
Saved session as th_01xyz.
bash
codex resume th_01xyz

Output: (TUI opens at that session's end state)


Working directory and git checks

codex exec refuses to run outside a git repository by default — a guard against accidental edits to a random directory. Override with --skip-git-repo-check:

bash
mkdir /tmp/scratch && cd /tmp/scratch
codex exec --skip-git-repo-check "Create a hello-world Flask app"

Output:

text
[agent creates app.py, requirements.txt, README.md]

Run in a different directory without cd:

bash
codex exec --cd /home/alice/work/api "Run the test suite"

Output:

text
[agent runs pytest in /home/alice/work/api]

Combining with --full-auto

--full-auto plus codex exec is the canonical "hands-free" pattern: workspace-confined writes, no network unless explicitly allowed, no interactive prompts (since exec defaults to --ask-for-approval never).

bash
codex exec --full-auto "Fix all ruff lint errors in src/"

Output:

text
[agent reads files, runs ruff --fix, edits remaining issues, exits]

Per-profile shorthand:

toml
[profiles.ci]
model              = "gpt-4o-mini"
approval_policy    = "never"
sandbox_mode       = "workspace-write"
bash
codex exec -p ci "Fix all type errors in src/"

Output:

text
[agent output; exits 0 on success]

Programmatic consumers — parsing stream-json

The richest output format for tools that need to react to events in real time. Each line is a self-contained JSON object whose type field tells you what it is.

Event types

TypePayload
session-startsession_id, model, cwd, profile
agent-turn-startturn_id
tool-calltool, input (tool-specific)
tool-resulttool, output, success
agent-message-deltadelta (streaming text chunk)
agent-turn-completeturn_id, last_message, tokens
session-endsession_id, exit_code, total_tokens
errorcode, message

Stream tokens as they arrive

bash
codex exec --json "Write a haiku" | jq -j 'select(.type=="agent-message-delta") | .delta'
echo

Output:

text
Lines write themselves
Syllables fall into place
Codex paints the page

Count tool calls by type

bash
codex exec --json "Refactor the auth module" \
  | jq -r 'select(.type=="tool-call") | .tool' \
  | sort | uniq -c

Output:

text
  12 shell
   8 read_file
   4 write_file

React to errors

bash
codex exec --json "Risky task" \
  | tee codex-stream.jsonl \
  | jq -rc 'select(.type=="error") | "ERROR: " + .message' >&2

Output (stderr):

text
ERROR: sandbox denied write to /etc/hosts

Combining exec with shell pipelines

codex exec is designed to be one node in a Unix pipeline. Read from stdin, write to stdout, exit with a code — that's it.

Filter through Codex

bash
git log --since=yesterday --oneline \
  | codex exec --output-last-message "Group these commits by area" \
  | tee yesterday-summary.txt

Output:

text
Auth: 3 commits — JWT migration, rate limit, refresh fix
DB:   2 commits — pool config, retry helper
Docs: 1 commit  — README update

Two-stage pipeline

bash
git diff main...HEAD \
  | codex exec --output-last-message "Extract a one-line summary of each functional change" \
  | codex exec --output-last-message "Format as a markdown bullet list"

Output:

text
- Replaced session cookies with JWT
- Added per-IP rate limit middleware
- Tightened DB pool config

Fan-out one prompt to many files

bash
for f in src/**/*.py; do
  result=$(codex exec --output-last-message --skip-git-repo-check \
    "Suggest one improvement for this file" < "$f")
  printf '%-30s  %s\n' "$f" "$result"
done

Output:

text
src/auth.py                   Extract token validation into a helper
src/db.py                     Replace string SQL with parameterised
src/services.py               Add type hints to handle_request

CI integration patterns

GitHub Actions — fail PR if Codex finds issues

yaml
- name: AI review
  env:
    OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
  run: |
    git diff origin/main...HEAD \
      | codex exec --output-format json --sandbox read-only --ask-for-approval never \
          "Find correctness bugs in this diff. Reply with JSON array of objects {file, line, issue}." \
      | jq -e '.last_message | fromjson | length == 0'

Output (when issues found):

text
[{"file":"src/auth.py","line":42,"issue":"==-comparison of tokens"}]
Process completed with exit code 1.

Pre-commit hook — auto-fix and re-stage

bash
#!/usr/bin/env bash
# .git/hooks/pre-commit
codex exec --full-auto -p ci "Run ruff format and fix any mypy errors in the staged files."
git add -u

Output:

text
[agent fixes issues; commit proceeds with re-staged files]

Daily summary cron job

bash
# crontab entry
0 18 * * * cd ~/work/myproject && codex exec --sandbox read-only --ask-for-approval never \
  "Summarise today's commits and open PRs" | mail -s "Daily summary" alice@example.com

Output: (none — email delivered with summary in body)

Self-healing test fixer

bash
pytest -x || codex exec --full-auto "Fix the failing test you just saw"

Output:

text
[agent reads the pytest traceback, locates the bug, edits the file]

Reading and writing structured data

Generate JSON from a description

bash
codex exec --output-last-message "Generate a JSON object with 3 fake users: id, name, email. JSON only, no prose." | jq .

Output:

text
[
  {"id": 1, "name": "Alice Dev", "email": "alice@example.com"},
  {"id": 2, "name": "Bob Eng",   "email": "bob@example.com"},
  {"id": 3, "name": "Cara Ops",  "email": "cara@example.com"}
]

Round-trip a YAML through Codex

bash
cat config.yaml \
  | codex exec --output-last-message "Convert this YAML to a flat .env file" \
  > .env.from-yaml

Output: (none — writes .env.from-yaml)


Common pitfalls

  1. codex exec defaults to --ask-for-approval never. This is the safe-for-CI default but it's the opposite of interactive mode. If you want a pipe-and-prompt behaviour, pass --ask-for-approval on-request explicitly — but be aware it will hang in CI without a tty.

  2. stdin piping has a hard limit (~1 MB by default). Massive diffs or log files truncate silently. Use --input-file <path> for larger inputs.

  3. --output-last-message swallows tool errors. A failed tool call still produces a final assistant message; you can't distinguish "succeeded" from "tried and gave up" by output alone. Check the exit code, or use --json and inspect agent-turn-complete.

  4. $(codex exec …) in bash strips trailing newlines. If your downstream consumer needs them, write to a file with > /tmp/out instead.

  5. codex exec does NOT inherit your TUI's /permissions state. Each invocation starts with the policy from config.toml and CLI flags. Don't assume "I approved this command interactively, so exec won't ask."

  6. A pre-existing background process (e.g., npm run dev &) is killed when exec exits. Codex's sandbox cleanup is aggressive. Use nohup + disown if you genuinely need a process to survive.

  7. --max-turns 1 doesn't mean "single API call." It means "one agent loop iteration" — which can still include tool calls. Use --max-turns 0 to disable tools entirely (model-only response).

  8. JSON output buffers until completion (with --output-format json). Use --json (stream-json) if you need to react during the run.

  9. exec runs from cwd, not the project root. If you launch from a subdirectory, the sandbox boundary follows. Combine with --cd to anchor to a known root.

  10. No tty means no progress indicators. Codex detects the non-interactive context and disables the spinner; if you see "frozen-looking" output, that's just because there's no animation.


Real-world recipes

One-liner repo summary

bash
codex exec --sandbox read-only --ask-for-approval never --output-last-message \
  "Summarise this repo in 3 sentences"

Output:

text
This repo is a personal cheat-sheet site built with Astro 4 and Tailwind CSS. Content lives in Markdown under src/content/. Cloudflare Pages auto-deploys on every push to main.

Replace grep | xargs sed with grep | codex

For mechanical transforms that are easier to describe than to encode:

bash
rg -l "TODO: legacy" src/ | xargs -I{} codex exec --full-auto \
  "Replace the 'TODO: legacy' marker in {} with a real implementation"

Output:

text
[for each file, agent edits and reports]

Streaming SSE to a Slack thread

bash
codex exec --json "Long task" \
  | jq -r 'select(.type=="agent-turn-complete") | .last_message' \
  | xargs -I{} curl -sS -X POST -H 'Content-Type: application/json' \
      -d "{\"text\":\"{}\"}" "$SLACK_WEBHOOK_URL"

Output:

text
[Slack messages appear as turns complete]

Lock the model and profile via env

bash
export CODEX_MODEL=gpt-5
export CODEX_PROFILE=ci
codex exec "Fix all mypy errors"

Output:

text
[agent runs with the locked model + profile]

Capture full transcript to a file

bash
codex exec --json "Refactor auth" > /tmp/run-$(date +%s).jsonl

Output: (none — JSONL written)

Re-derive a human transcript later:

bash
jq -r 'select(.type=="agent-turn-complete") | .last_message' /tmp/run-*.jsonl

Output:

text
[final messages from each turn]

Diff explainer for code reviews

bash
git show HEAD | codex exec --output-last-message \
  "Explain what this commit changed and why, in 4 bullets"

Output:

text
- Introduced JWT helpers in src/auth/jwt.py
- Replaced session-cookie lookups in middleware
- Added refresh-token rotation logic
- Updated tests/test_auth.py to cover the new flow

Translate a stack trace into a fix proposal

bash
pytest 2>&1 | codex exec --output-last-message \
  "Read this pytest failure and propose the smallest fix. Output diff only."

Output:

text
--- a/src/auth.py
+++ b/src/auth.py
@@ -38,3 +38,3 @@
-    if token == expected:
+    if secrets.compare_digest(token, expected):

Idempotent retry wrapper

bash
# Retry up to 3 times with exponential backoff
for i in 1 2 3; do
  codex exec --timeout 60 --full-auto "Fix the flaky test in tests/test_auth.py" && break
  sleep $((2**i))
done

Output:

text
[agent attempts until success or 3 tries elapsed]

Multi-file fan-out with GNU parallel

bash
ls src/*.py | parallel -j 4 'codex exec --full-auto --skip-git-repo-check "Add docstrings to {}"'

Output:

text
[4 agents run in parallel; each edits its own file]