Codex Exec in a Script: The Flags, the Exit Codes and a Timeout
What the docs say codex exec does in automation, and the small wrapper I put around it so a stalled run ends and an empty result is never read as a pass.

codex exec is how you run Codex from a script instead of the interactive screen: codex exec "summarize the repository structure" prints the final answer to stdout, and cat prompt.txt | codex exec - reads the whole prompt from stdin. I call it from a review step, and the work was in what the script does when Codex never answers. On one run it did not answer for 2 hours 27 minutes, using 0.10 seconds of CPU the whole time. The page I read does not describe a timeout, so I wrapped the call in one.
Updated 2026-10-26. Docs quoted: Non-interactive mode, fetched 2026-10-26. The wrapper details come from my own script.
What codex exec does, per the docs
The docs say that "Non-interactive mode lets you run Codex from scripts (for example, continuous integration (CI) jobs) without opening the interactive TUI." They describe the output split this way: "While codex exec runs, Codex streams progress to stderr and prints only the final agent message to stdout." The default is also stated: "By default, codex exec runs in a read-only sandbox."
Two more behaviours matter in a script. If you leave out the prompt, "Codex reads the prompt from stdin", and codex exec - forces that. And outside a repository it stops: "Codex requires commands to run inside a Git repository to prevent destructive changes. Override this check with codex exec --skip-git-repo-check if you're sure the environment is safe."
The flags my review step uses
Four documented options carry the whole review step, and none of them limits how long it runs.
| Option | What the docs say | Why I use it |
|---|---|---|
- (prompt on stdin) | "Codex reads the prompt from stdin" | A long prompt does not hit an argument-length limit |
--sandbox read-only | The default; --sandbox workspace-write allows edits | A review should not change the repository |
-o <path> / --output-last-message <path> | "writes the final message to the file and still prints it to stdout" | I read the verdict from a file, not from a pipe |
--skip-git-repo-check | Overrides the Git requirement "if you're sure the environment is safe" | I call the wrapper from inside the repo under review, so I relax this check myself |
The full flag list is in the docs' codex exec reference. The wrapper also sets an approval policy of never, so a prompt for permission cannot block the run, and feeds the prompt from a file, so stdin is never a terminal waiting for typed input. I did not read that reference, so the table is only what my step uses.
The hang
My script's header records it. A review sat for 2 hours 27 minutes and used 0.10 seconds of CPU. The tail was a separate mistake: I had piped the output through it, and tail holds everything until the stream ends, so the output looked empty and nothing showed whether the run was stuck. It hid the problem; it did not cause it. I do not know why the stream stalled, and I did not find a documented timeout option on the page above. So the wrapper supplies one: it starts codex exec in the background, checks once a second, and past the deadline stops the process and its children with SIGTERM, then SIGKILL. A stall looks like long wall time with almost no CPU time, which is how I recognised it afterwards. I pass 600 seconds. The shape, stripped to the core:
TIMEOUT_SECS=600; prompt=prompt.txt; log=run.log; last_msg=verdict.txt
codex exec - -s read-only -o "$last_msg" < "$prompt" > "$log" 2>&1 &
pid=$!; waited=0
while kill -0 "$pid" 2>/dev/null; do
if (( waited >= TIMEOUT_SECS )); then
kill "$pid"; sleep 3; kill -9 "$pid" 2>/dev/null; exit 124
fi
sleep 1; (( waited++ ))
done
wait "$pid"; rc=$?
[ "$rc" -eq 0 ] && [ -s "$last_msg" ] && exit 0
exit 1
Mine also kills the whole process tree, not just the one process; this excerpt does not.
What I changed: the call gets a deadline I control, and the verdict goes to a file, not through tail. The docs themselves show piping codex exec output, so this is my fix for my incident, not a rule.
Exit codes: no verdict is never a pass
The wrapper's exit codes, apart from 2 for a usage error such as a missing prompt file:
- 0: a non-empty final message was produced after a clean exit.
- 124: it timed out (and so did the fallback reviewer, if that was enabled).
- 1: no verdict for any other reason, such as a failed run or a quota error.
A caller treats 124 and 1 the same way: there is no review, so there is no pass. The wrapper checks the exit and the file, and nothing more: it does not parse the message. I ask for a final line of VERDICT: PASS or VERDICT: FAIL in the prompt, as the review post describes, and the rule for whoever calls the wrapper is that any other last line is no pass. The wrapper does not enforce that rule. That is the same rule I use for agents' own claims in Don't Trust an Agent That Says the Tests Passed.
A false positive I fixed
When a run produced no verdict, the wrapper scans the log for rate-limit words (rate limit, quota, 429, and similar) to classify the failure as a quota problem or something else. Early on it scanned the log even after a verdict had been written, and Codex echoes the code it reviews. A line number such as 429 in the reviewed code looked like an HTTP error, and a good verdict was thrown away. The fix is the order of checks: a clean exit with a non-empty final message is accepted first, and the log is read for a cause only otherwise. Falling back to a substitute reviewer is covered in the rate-limit ladder post; in my wrapper that fallback is off unless a caller turns it on, and when on, its output is labelled so it is never mistaken for a Codex run.
What I did not measure
I have no count of how often the timeout fired, and I did not compare codex exec with any other review tool. The hang and the false positive are the two incidents my script's comments record; they are not a sample. Nothing here says how long a Codex review should take. The docs also describe an --output-schema option for structured output, which I do not use.
Related: Claude Code Hooks: Three I Run Every Day and Flaky Tests or No Result? Seven Bad Runs, Sorted.
Keep reading
- Using Codex as the Read-Only Reviewer for Claude CodeNot a benchmark: Claude Code writes and orchestrates, Codex reviews. The split, the verdict contract, and one case where the gate said no.
- Claude Code Hooks: Three I Run Every DayWhich event does which job, what the JSON in and the exit code out actually mean, and the three guards I attach to PreToolUse, Stop and SessionEnd, with what each one caught.
- Claude Code Skills: How to Write Ones Your Agent Actually UsesWhy AI coding agents ignore skills, how to write descriptions that trigger in practice, and lessons from maintaining 130 skills across three active profiles.