Skip to content

Latest commit

 

History

19 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

worker-codex

test

A watchdog wrapper for the OpenAI Codex CLI that keeps codex exec from hanging forever in unattended automation.

codex exec is great in a terminal, where a human notices when it stops responding. In cron jobs, CI steps, and headless agent pipelines there is nobody watching - a stuck run just sits there holding a process slot until someone finds it hours later. worker-codex puts a time limit on the run, kills the whole process tree when it trips, and reports why it stopped through a standard exit code so the calling script can decide what to do next.

worker-codex --timeout 900 exec --sandbox workspace-write "refactor the parser"
echo $?   # 0 = finished, 124 = hit the time limit, 125 = went silent

What it actually does

Closes stdin by default. This is the single most common cause of a hung codex exec. If stdin stays open, Codex can sit waiting for input that will never arrive. worker-codex redirects stdin from an empty file unless you explicitly pass --stdin <file>.

Enforces a wall-clock limit (--timeout, default 900s). When the limit trips, the process is terminated - and so are its children and grandchildren. Codex spawns subprocesses; killing only the parent leaves orphans behind. The bash version uses process-group signalling (kill -TERM -PGID) plus a pkill -P sweep; the PowerShell version uses taskkill /T /F.

Optionally watches for silence (--stall, default off). If the log file stops growing for N seconds, the run is treated as stuck. This is off by default on purpose - see Why --stall defaults to off.

Reports the reason as an exit code, so automation can branch on it without parsing text:

Code Meaning
0 Codex finished normally
2 Bad usage - unknown value, missing argument, unreadable --stdin file
124 Wall-clock limit exceeded, process tree killed
125 No output for --stall seconds, process tree killed
other Passed through from Codex itself

It also prints a language-neutral status line that automation can parse without reading the human-facing summary:

worker-codex: status=timeout exit_code=124 elapsed_sec=15 log=/home/you/.codex-runs/20260724-233844-2986093.log

Writes a standard log to ~/.codex-runs/<timestamp>-<pid>.log and prints the path in its summary, so a failed run leaves a breadcrumb you can pick up later. The directory is created 0700 and logs 0600, because the command line - which may carry your prompt - is recorded in the header. Logs older than 14 days are pruned automatically.

Adds --skip-git-repo-check to exec calls. Codex refuses to run outside a git repository, which is a surprising failure mode for a cron job working in a scratch directory. The flag is injected only for the exec subcommand, and only when you have not already passed it.

Why not just use timeout?

For the simple case, timeout really is enough, and you should use it:

timeout -k 10 900 codex exec "..." < /dev/null

That covers the wall-clock limit and closing stdin. worker-codex exists for the parts it does not cover:

  • timeout signals one process, not the tree. Codex spawns subprocesses, and this is a known, still-open problem in Codex itself - when a run is killed, orphaned children keep the stdout/stderr pipes open, which is exactly what makes an "already terminated" job keep hanging. worker-codex signals the whole process group and escalates to SIGKILL for children that ignore SIGTERM.
  • timeout cannot see a stalled-but-alive process. A Codex run that stops making progress while still holding its process is not distinguishable from a slow one by wall clock alone. --stall watches output instead - off by default, for reasons explained below.
  • timeout returns 124 for everything. There is no way for the caller to tell "hit the limit" from "went silent", which matters when you want to retry one and escalate the other.
  • No breadcrumbs. When a cron job fails at 4am, timeout leaves nothing behind. worker-codex writes a log with the exact command and prints its path.

If none of that matters to you, timeout is the smaller dependency and the right answer.

Install

There is nothing to build. Drop the script somewhere on your PATH and make it executable.

Linux / macOS

curl -fsSL https://raw.githubusercontent.com/renovys/worker-codex/main/worker-codex -o ~/.local/bin/worker-codex
chmod +x ~/.local/bin/worker-codex

Windows (PowerShell 5.1)

Invoke-WebRequest -Uri https://raw.githubusercontent.com/renovys/worker-codex/main/worker-codex.ps1 `
  -OutFile "$HOME\.claude\worker-codex.ps1"

Requires codex on your PATH (or point at it with --codex-bin). The bash version targets bash 3.2, so it runs on stock macOS without installing a newer bash.

The bash version's default automatic account routing remains a single-file install. It first tries the optional gpt_quota.py and quota_balance.py helper modules when they are available on the Python import path; otherwise, it uses the minimal implementations embedded in worker-codex. The helpers are not required for a clean install. Automatic routing needs at least one GPT quota snapshot from a Codex CLI run in ~/.codex/sessions/**/*.jsonl, ~/.model-usage/gpt-*.json, ~/.codex-biz/sessions/**/*.jsonl, or ~/.model-usage/gptbiz-*.json. If no snapshot exists, it exits 2 with the files checked and the action needed to create one.

Usage

worker-codex [--timeout SEC] [--stall SEC] [--stdin FILE] [--tail N] [--codex-bin PATH]
          [--help] [--version] [--] <codex args...>
Flag Default Description
--timeout SEC 900 Wall-clock limit, minimum 1. On exceed: kill tree, exit 124.
--stall SEC 0 (off) Kill if the log has not grown for SEC seconds; exit 125.
--stdin FILE (empty) Feed FILE to Codex on stdin instead of closing it. Must be a regular file.
--tail N 40 Lines of Codex output to echo in the summary.
--codex-bin PATH codex Path to the Codex binary.
--help Print this usage and exit 0 without calling Codex.
--version Print the wrapper version and exit 0.
-- Stop parsing wrapper flags; everything after goes to Codex.

Defaults can also come from the environment - CODEX_RUN_TIMEOUT, CODEX_RUN_STALL, CODEX_RUN_TAIL, CODEX_BIN - on both platforms. Values are validated the same way as command-line flags, so a typo in a cron environment fails loudly with exit 2 instead of silently disabling the limit.

On Windows the flags are identical, so the same call shape works on all three platforms:

powershell -NoProfile -File $HOME\.claude\worker-codex.ps1 --timeout 900 exec --sandbox workspace-write "your prompt"

Passing a long prompt

Prompts with quotes, newlines, or shell metacharacters are much safer in a file than on the command line:

worker-codex --timeout 1200 --stall 0 --stdin ./prompt.md \
  exec --model gpt-5.6-sol --output-last-message ./answer.md

Note the pairing with --output-last-message: Codex's stdout is a long reasoning-and-tooling transcript, while the final answer goes to that file. Reading the transcript to decide whether the run "worked" is a trap - check the exit code and the answer file instead.

Reacting to the outcome

worker-codex --timeout 600 exec "$PROMPT"
case $? in
  0)   echo "done" ;;
  124) echo "timed out - resuming manually" ;;
  125) echo "went silent - resuming manually" ;;
  *)   echo "codex failed with $?" ;;
esac

Why --stall defaults to off

Stall detection watches the log file's modification time. That is a good proxy for "is it still working" only when the run produces steady output. Codex spends most of its wall-clock time in model inference, and a long reasoning step can legitimately produce no output for several minutes - in measured runs, over 90% of elapsed time was inference wait, with individual quiet stretches running into the minutes on large contexts.

So the rule of thumb is:

  • Leave --stall off for calls where the answer arrives at the end (--output-last-message), or where you expect long reasoning. Rely on --timeout alone.
  • Turn --stall on only for interactive-style coding runs that stream progress continuously, where silence really does mean something is wrong.

Turning it on for a quiet-by-design call will kill healthy runs.

Logs

Every run appends to ~/.codex-runs/<timestamp>-<pid>.log, starting with a header that records the limits and the exact command line. The summary block printed at the end tells you what happened, how long it took, and where the full log is:

----- worker-codex summary -----
status: wall-clock limit of 900s exceeded - terminated; caller should take over
decision at: 900s / total elapsed: 905s / full log: /home/you/.codex-runs/20260724-213035-2542577.log
worker-codex: status=timeout exit_code=124 elapsed_sec=905 log=/home/you/.codex-runs/20260724-213035-2542577.log

The fields report the stop reason, when the decision was made, total elapsed time including the kill grace period, and the full log path; parse the final worker-codex: status=... line in automation.

If a process somehow survives the kill, the summary says so explicitly rather than reporting a clean stop.

Platform support

Platform Shell Status
Linux bash 5.x Verified - 26/26
macOS bash 3.2 (stock) Verified - 26/26
Windows PowerShell 5.1 Verified - 18/18

The suites run against stub binaries, so they never call the real Codex and cost nothing:

./test/run-tests.sh              # Linux / macOS
.\test\run-tests.ps1             # Windows

They cover normal exit, exit-code pass-through, timeout, stall, cleanup of grandchildren (including one that ignores SIGTERM), every argument-validation rule, explicit account selection, CODEX_HOME preservation, clean-HOME helper fallback, --help/--version, stdin delivery, the machine-readable status line, and log permissions. CI runs the bash suite on Ubuntu and macOS and the PowerShell suite on Windows.

License

MIT - see LICENSE.

About

Watchdog wrapper for the OpenAI Codex CLI — stops `codex exec` from hanging forever in cron jobs and headless pipelines. bash + PowerShell.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages