Ramiz RajaBook a call
← Writing
Working With AI Agents

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.

Ramiz RajaOctober 26, 20265 min read
Codex Exec in a Script: The Flags, the Exit Codes and a Timeout

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.

OptionWhat the docs sayWhy I use it
- (prompt on stdin)"Codex reads the prompt from stdin"A long prompt does not hit an argument-length limit
--sandbox read-onlyThe default; --sandbox workspace-write allows editsA 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-checkOverrides 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.