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
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.
codex exec "What does this repo do?"
Output:
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.
git diff main...HEAD | codex exec --output-last-message "Summarise this diff for a PR description"
Output:
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:
cat src/auth.py | codex exec "Review this file for security issues; output bullets only"
Output:
- 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:
echo "Write a haiku about TOML" | codex exec
Output:
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.
| Format | Flag | Description |
|---|---|---|
| Text | --output-format text (default) | Streamed plain text with diff previews |
| Last message | --output-last-message | Only the final assistant message, plain text |
| JSON | --output-format json | Single JSON object printed after completion |
| Stream JSON | --output-format stream-json | Newline-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.
codex exec "Add a docstring to every public function in src/utils.py"
Output:
[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.
title=$(codex exec --output-last-message "Suggest a 6-word PR title for the staged diff" < <(git diff --cached))
echo "$title"
Output:
Refactor auth module to use JWT
JSON output
A single JSON object with full session metadata, printed atomically after the agent finishes.
codex exec --output-format json "List three Linux process-inspection tools as JSON"
Output:
{"type":"result","subtype":"success","session_id":"th_01abc","turn_count":1,"last_message":"[\"ps\",\"top\",\"lsof\"]","exit_code":0}
Parse with jq:
codex exec --output-format json "Run pytest and report the count" | jq -r '.last_message'
Output:
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.
codex exec --json "Run the test suite and summarise failures"
Output:
{"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:
codex exec --json "What does this repo do?" | jq -r 'select(.type=="agent-turn-complete") | .last_message'
Output:
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:
| Code | Meaning |
|---|---|
0 | Success — agent completed and produced a final message |
1 | Generic error — model error, network error, or unrecognised failure |
2 | Approval denied — user (or hook) refused a required tool call |
3 | Sandbox violation — agent attempted a denied operation and could not recover |
4 | Authentication error — invalid or missing OPENAI_API_KEY / expired token |
5 | Configuration error — config.toml failed to parse |
6 | Timeout — --timeout exceeded |
7 | Max-turns exceeded — --max-turns N reached before the agent finished |
130 | Cancelled — user pressed Ctrl+C (SIGINT) |
Use exit codes in pipelines:
codex exec --full-auto "Fix all ruff issues" || echo "Codex run failed with code $?" >&2
Output:
[agent fixes issues; exits 0 on success]
Fail-fast in a chain:
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:
[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.).
| Flag | Description |
|---|---|
--output-format <fmt> | text / json / stream-json |
--output-last-message | Equivalent to --output-format last-message |
--json | Alias 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 |
--ephemeral | Don't write the session to history |
--save | Persist the session even though it's non-interactive |
--skip-git-repo-check | Allow running outside a git repo |
--full-auto | Shorthand 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
codex exec --timeout 120 "Fix any failing tests"
Output:
[agent runs for up to 120 seconds; exits with code 6 if not done in time]
Max-turns cap
codex exec --max-turns 8 "Fix any failing tests"
Output:
[agent runs for at most 8 tool-use cycles; exits with code 7 if it hits the cap]
Combine both
codex exec --timeout 300 --max-turns 20 "Refactor the auth module"
Output:
[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.
codex exec --save "Refactor the DB layer"
Output:
[agent output]
Saved session as th_01xyz.
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:
mkdir /tmp/scratch && cd /tmp/scratch
codex exec --skip-git-repo-check "Create a hello-world Flask app"
Output:
[agent creates app.py, requirements.txt, README.md]
Run in a different directory without cd:
codex exec --cd /home/alice/work/api "Run the test suite"
Output:
[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).
codex exec --full-auto "Fix all ruff lint errors in src/"
Output:
[agent reads files, runs ruff --fix, edits remaining issues, exits]
Per-profile shorthand:
[profiles.ci]
model = "gpt-4o-mini"
approval_policy = "never"
sandbox_mode = "workspace-write"
codex exec -p ci "Fix all type errors in src/"
Output:
[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
| Type | Payload |
|---|---|
session-start | session_id, model, cwd, profile |
agent-turn-start | turn_id |
tool-call | tool, input (tool-specific) |
tool-result | tool, output, success |
agent-message-delta | delta (streaming text chunk) |
agent-turn-complete | turn_id, last_message, tokens |
session-end | session_id, exit_code, total_tokens |
error | code, message |
Stream tokens as they arrive
codex exec --json "Write a haiku" | jq -j 'select(.type=="agent-message-delta") | .delta'
echo
Output:
Lines write themselves
Syllables fall into place
Codex paints the page
Count tool calls by type
codex exec --json "Refactor the auth module" \
| jq -r 'select(.type=="tool-call") | .tool' \
| sort | uniq -c
Output:
12 shell
8 read_file
4 write_file
React to errors
codex exec --json "Risky task" \
| tee codex-stream.jsonl \
| jq -rc 'select(.type=="error") | "ERROR: " + .message' >&2
Output (stderr):
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
git log --since=yesterday --oneline \
| codex exec --output-last-message "Group these commits by area" \
| tee yesterday-summary.txt
Output:
Auth: 3 commits — JWT migration, rate limit, refresh fix
DB: 2 commits — pool config, retry helper
Docs: 1 commit — README update
Two-stage pipeline
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:
- Replaced session cookies with JWT
- Added per-IP rate limit middleware
- Tightened DB pool config
Fan-out one prompt to many files
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:
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
- 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):
[{"file":"src/auth.py","line":42,"issue":"==-comparison of tokens"}]
Process completed with exit code 1.
Pre-commit hook — auto-fix and re-stage
#!/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:
[agent fixes issues; commit proceeds with re-staged files]
Daily summary cron job
# 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
pytest -x || codex exec --full-auto "Fix the failing test you just saw"
Output:
[agent reads the pytest traceback, locates the bug, edits the file]
Reading and writing structured data
Generate JSON from a description
codex exec --output-last-message "Generate a JSON object with 3 fake users: id, name, email. JSON only, no prose." | jq .
Output:
[
{"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
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
-
codex execdefaults 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-requestexplicitly — but be aware it will hang in CI without a tty. -
stdin piping has a hard limit (~1 MB by default). Massive diffs or log files truncate silently. Use
--input-file <path>for larger inputs. -
--output-last-messageswallows 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--jsonand inspectagent-turn-complete. -
$(codex exec …)in bash strips trailing newlines. If your downstream consumer needs them, write to a file with> /tmp/outinstead. -
codex execdoes NOT inherit your TUI's/permissionsstate. Each invocation starts with the policy fromconfig.tomland CLI flags. Don't assume "I approved this command interactively, so exec won't ask." -
A pre-existing background process (e.g.,
npm run dev &) is killed when exec exits. Codex's sandbox cleanup is aggressive. Usenohup+disownif you genuinely need a process to survive. -
--max-turns 1doesn't mean "single API call." It means "one agent loop iteration" — which can still include tool calls. Use--max-turns 0to disable tools entirely (model-only response). -
JSON output buffers until completion (with
--output-format json). Use--json(stream-json) if you need to react during the run. -
execruns from cwd, not the project root. If you launch from a subdirectory, the sandbox boundary follows. Combine with--cdto anchor to a known root. -
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
codex exec --sandbox read-only --ask-for-approval never --output-last-message \
"Summarise this repo in 3 sentences"
Output:
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:
rg -l "TODO: legacy" src/ | xargs -I{} codex exec --full-auto \
"Replace the 'TODO: legacy' marker in {} with a real implementation"
Output:
[for each file, agent edits and reports]
Streaming SSE to a Slack thread
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:
[Slack messages appear as turns complete]
Lock the model and profile via env
export CODEX_MODEL=gpt-5
export CODEX_PROFILE=ci
codex exec "Fix all mypy errors"
Output:
[agent runs with the locked model + profile]
Capture full transcript to a file
codex exec --json "Refactor auth" > /tmp/run-$(date +%s).jsonl
Output: (none — JSONL written)
Re-derive a human transcript later:
jq -r 'select(.type=="agent-turn-complete") | .last_message' /tmp/run-*.jsonl
Output:
[final messages from each turn]
Diff explainer for code reviews
git show HEAD | codex exec --output-last-message \
"Explain what this commit changed and why, in 4 bullets"
Output:
- 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
pytest 2>&1 | codex exec --output-last-message \
"Read this pytest failure and propose the smallest fix. Output diff only."
Output:
--- a/src/auth.py
+++ b/src/auth.py
@@ -38,3 +38,3 @@
- if token == expected:
+ if secrets.compare_digest(token, expected):
Idempotent retry wrapper
# 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:
[agent attempts until success or 3 tries elapsed]
Multi-file fan-out with GNU parallel
ls src/*.py | parallel -j 4 'codex exec --full-auto --skip-git-repo-check "Add docstrings to {}"'
Output:
[4 agents run in parallel; each edits its own file]