Quick answer
codex exec is the Codex CLI entry point for non-interactive scripts and CI. Supply the prompt as an argument or through standard input, set the workspace with -C, and choose the least-powerful sandbox that can complete the task. The command streams progress to standard error and prints the final agent message to standard output, so those streams should be captured separately.
codex exec \
-C /absolute/path/to/repository \
-m YOUR_MODEL_ID \
-c model_reasoning_effort=high \
-s workspace-write \
-o codex-final.txt \
"Implement the bounded task in TASK.md, run the named checks, and report evidence." \
2>codex-progress.log
status=$?
printf 'codex exit code: %s\n' "$status"
exit "$status"
Replace the model placeholder with an ID available to your account and CLI version. Model availability changes; do not copy a model name from an old article and assume it is selectable.
Run Codex headlessly with explicit flags
Headless does not mean “run the interactive interface without a terminal.” It means invoking the dedicated exec subcommand. There is no prompt box for follow-up questions, so the initial command must contain enough context and a finish condition.
Choose the workspace, model, effort, and sandbox
| Flag | Purpose | Automation rule |
|---|---|---|
| -C DIR | Sets the workspace root before execution | Prefer an absolute, prevalidated path |
| -m MODEL | Overrides the configured model for this run | Pin intentionally; verify availability separately |
| -c key=value | Overrides one configuration value | Use repeatable, reviewable overrides |
| -s MODE | Selects read-only, workspace-write, or danger-full-access | Grant only what the task needs |
| --skip-git-repo-check | Allows execution outside a Git repository | Use only when the directory is intentionally non-Git |
The reasoning override requested in many automation recipes is -c model_reasoning_effort=high. It is a configuration override, not a guarantee that every model supports every effort value. Match the value to the selected model and test the exact command in your environment.
Pass a prompt file through stdin
codex exec - \
-C "$PROJECT_DIR" \
-s workspace-write \
< TASK.md \
> codex-final.txt \
2> codex-progress.log
The hyphen explicitly tells Codex to read the entire prompt from stdin. You can also provide a prompt argument while piping data; in that form, the argument remains the instruction and the piped text becomes additional context. Avoid passing secrets or an unfiltered repository dump merely because stdin makes it easy.
Capture output, logs, and exit codes without losing evidence
A reliable wrapper preserves three different signals: the final answer, the progress log, and the process exit status. Standard shell redirection is enough. The -o or --output-last-message flag also writes the final agent message to a file while leaving it on stdout.
#!/usr/bin/env bash
set -u
final_file="artifacts/codex-final.md"
log_file="artifacts/codex-progress.log"
mkdir -p artifacts
codex exec \
-C "$PWD" \
-s read-only \
-o "$final_file" \
"Review the current diff. Do not edit files. List blocking findings with paths." \
> artifacts/codex-stdout.txt \
2> "$log_file"
status=$?
if [ "$status" -ne 0 ]; then
printf 'Codex failed; inspect %s\n' "$log_file" >&2
fi
exit "$status"
Do not append || true to the Codex command unless failure is deliberately non-blocking and recorded elsewhere. That pattern converts a failed agent run into shell success. If a pipeline uses tee, enable set -o pipefail or inspect the pipeline status explicitly; otherwise the last process can hide the Codex exit code.
For event-level automation, add --json. Stdout then becomes JSON Lines containing thread, turn, item, and error events. Parse each line independently instead of treating the stream as one JSON document.
Background codex exec without confusing “started” with “finished”
An ampersand only proves that the shell launched a process. Keep its PID, wait for it, and write the exit status after completion. A separate status file makes the result inspectable even when the parent terminal disconnects.
run_dir="runs/codex-$(date +%Y%m%d-%H%M%S)"
mkdir -p "$run_dir"
(
codex exec -C "$PWD" -s workspace-write - < TASK.md \
> "$run_dir/final.txt" \
2> "$run_dir/progress.log"
printf '%s\n' "$?" > "$run_dir/exit-code"
) &
pid=$!
printf '%s\n' "$pid" > "$run_dir/pid"
printf 'started PID %s in %s\n' "$pid" "$run_dir"
For a script that must stay attached, call wait "$pid" and propagate that result. For a job that must survive logout, use the process supervisor native to the environment—such as a CI runner, systemd, or a container orchestrator—rather than assuming a bare background job has durable ownership. Add an external timeout and a log freshness check; the CLI cannot prove that your surrounding process manager is healthy.
Avoid sandbox and approval pitfalls in unattended runs
The safe default for non-interactive execution is read-only. A task that must edit files needs -s workspace-write. If the task needs a path outside the workspace, grant a narrowly scoped additional directory where supported instead of opening the whole machine. Network access, credentials, managed policies, and configured tools may still impose separate restrictions.
- Use read-only for audits, summaries, and plans that require no repository mutation.
- Use workspace-write for bounded edits and tests contained within the selected workspace.
- Reserve danger-full-access or approval bypassing for an externally isolated runner. It is not a convenience flag for a normal workstation.
- Use --skip-git-repo-check only after verifying the intended directory. The Git check is a protective boundary, not an arbitrary error to suppress.
- Preinstall dependencies and authenticate before the run when possible. An agent cannot click an approval prompt that does not exist in headless mode.
Be equally cautious with credentials. Saved CLI authentication may be reused locally, while CI commonly supplies credentials explicitly. Keep secrets scoped to the Codex invocation and do not expose them to untrusted build scripts, dependency hooks, or checked-out code in the same environment.
Triage a failed unattended run before retrying
A retry is useful only when something material changes. First read the progress log and classify the failure. An authentication error needs a credential or account fix. A sandbox denial needs a narrower task or an intentional permission change. A missing command needs environment setup. A test failure after an edit needs a different code hypothesis. Repeating the same command without changing its inputs, permissions, environment, or task spec usually spends more time without adding evidence.
if [ ! -s artifacts/codex-final.md ]; then
printf 'No final message was produced\n' >&2
fi
if rg -n "permission denied|sandbox|authentication|rate limit" \
artifacts/codex-progress.log; then
printf 'Classify the blocker before retrying\n' >&2
fi
Do not treat a partially written file as a successful result. After any nonzero exit, inspect the working tree, preserve the log, and decide whether the changes are safe to keep. On the next attempt, record the changed variable—for example, “dependency installed,” “write scope reduced,” or “acceptance command corrected.” Cap automatic retries at a small number and escalate persistent failures instead of creating an unbounded loop.
Use a task specification that prevents avoidable stalls
A non-interactive task should answer the questions a human would otherwise resolve mid-run. Define one outcome, declare writable scope, name exact verification commands, describe what not to do, and supply a stop condition. Ask for evidence in the final message so completion can be checked by a wrapper or reviewer.
# Goal
Fix the failing parser test with the smallest production-code change.
# Scope
- You may edit: src/parser.ts, test/parser.test.ts
- Do not edit dependencies, generated files, CI, or unrelated formatting.
# Acceptance criteria
- `npm test -- --runInBand test/parser.test.ts` exits 0.
- Existing empty-input behavior remains unchanged.
- The final response lists changed files and the verification exit code.
# Execution rules
- Inspect nearby code and tests before editing.
- Do not ask questions. If evidence is insufficient, make no speculative edit.
- Do not publish, push, open a PR, or access external accounts.
# Stop conditions
- Stop after two materially different failed fix attempts.
- On a missing credential, unavailable dependency, or sandbox denial, report the
exact blocker and the smallest owner action required.
“Do whatever is needed” is not a task spec. It leaves scope, authority, retries, and proof undefined. A bounded spec also reduces the temptation to solve a permission problem by widening the sandbox. For longer work, add a time or cost ceiling and a named artifact path, then let the external supervisor enforce the timeout.
Production checklist for automated Codex CLI runs
- Resolve and validate the absolute workspace path.
- Verify authentication and the selected model before the job.
- Choose read-only or workspace-write explicitly.
- Keep progress logs, final output, and exit status separate.
- Set a supervisor timeout and a bounded retry policy.
- Review the diff and rerun deterministic checks outside the agent.
- Treat a zero exit code as process evidence, not proof the change is correct.