← All guides

codex exec Hangs Forever and Returns Nothing

The identical command finishes in a minute when you type it and hangs for ten minutes at 0.07s of CPU when a script runs it. No output, no error, no timeout. Codex is blocked waiting on a terminal that your script never gave it.

Automation stuck and you can't tell why?

Book a rescue session — we find the block live on a screen-share. No cause found, no charge.

Bottom Line

  • It is not slow. It is blocked. Codex is waiting on stdin from a TTY that does not exist.
  • Fix for scripts and cron: append </dev/null, or pass stdin=DEVNULL in subprocess.
  • Fix for detached background jobs: run it in the foreground instead, or under a pty. Redirecting stdin is not always enough once the job is fully detached.
  • Diagnose with CPU time, not wall time. Frozen CPU with climbing elapsed time means blocked, not busy.
  • Batches: sequential foreground calls beat one big backgrounded script.

How to Confirm It in Ten Seconds

Before changing anything, prove it is a block rather than slow work. Find the PID and look at CPU time:

pgrep -fl codex
ps -o pid,etime,time,stat,command -p <PID>

You are comparing two columns:

  • ETIME — how long the process has been alive.
  • TIME — how much CPU it has actually consumed.

A model call doing real work accumulates CPU seconds steadily. The failure looks like this: ETIME at 10:32 and TIME at 0:00.07. Ten minutes elapsed, seven hundredths of a second of CPU used. That process is not thinking. It is parked on a blocking read.

The STAT column corroborates it — an S state means interruptible sleep, which is what waiting on input looks like.

Case One: Scripts, Cron, and CI

The common case. Codex expects an interactive terminal and tries to read from standard input. In a non-interactive shell there is no TTY attached, the read never returns, and nothing times out because nothing has gone wrong from the process’s point of view. It is patiently waiting for you.

Fix — shell:

codex exec "refactor the parser module" </dev/null

Fix — Python:

import subprocess

subprocess.run(
    ["codex", "exec", "refactor the parser module"],
    stdin=subprocess.DEVNULL,     # this is the fix
    capture_output=True,
    text=True,
    timeout=900,                  # and always set one of these
)

The timeout is not optional in anything you schedule. Without it, one blocked call holds the entire job open until something else kills it, which on a nightly run means you find out in the morning.

Case Two: Fully Detached Background Jobs

There is a second, nastier variant. You background the whole thing — nohup ... &, a run_in_background helper, a detached worker — with output redirected to a file, and it stalls even though you handled stdin.

The observed shape: a backgrounded script wrapping codex exec sitting at about 0.07s of CPU for ten minutes with no output and no generated files, while the identical command run in the foreground completes in roughly a minute.

Closing stdin does not reliably rescue this one. What works:

  1. Run codex exec in the foreground. If the surrounding job needs to be scheduled, schedule the job; let Codex itself run in the foreground inside it.
  2. Or give it a pty. If you genuinely must detach, wrap it so a pseudo-terminal exists:
script -q /dev/null codex exec "your prompt" </dev/null
  1. For batches, go sequential. Ten foreground calls in a loop are more reliable than one detached script doing ten things. Slower on paper, finishes in practice.

Why Wrapping It in a Timeout Is Not a Fix

The tempting move is:

timeout 300 codex exec "..." || echo "gave up"

That converts a hang into a failure, which is an improvement for your logs and no improvement at all for your output. You still get nothing done, now on a schedule. Use timeout as a backstop alongside the stdin fix, never instead of it.

The General Rule

This is one instance of a pattern worth internalizing, because every CLI-shaped agent tool has some version of it:

A command-line tool built for humans assumes a human is attached.

Interactive tools reach for stdin, check isatty(), emit ANSI escapes, prompt for confirmation, and open pagers. All of those behave differently or block outright when the terminal is gone. When any CLI works by hand and hangs in automation, the first question is not “what is slow” but “what is it waiting for.” The answer is usually input, and usually there is a flag or a redirect that says “nobody is coming.”

Tried both fixes and it still stalls?

Then it's the sandbox scope or the provider config, and those are harder to see. Book a rescue session.

Need OpenClaw fixed live?

Remote rescue sessions for gateway, auth, tunnel, VPS, and model access problems.

See Rescue Session

Read next

Codex Says the Model Is Not Supported Before It Starts
Codex fails before reading the task because config.toml selects a model the installed CLI cannot use. Update Codex or override the model.
Is Codex Fast Mode Worth It? 2.5x the Credits for 1.5x the Speed (I Tested It Live)
Codex Fast Mode runs your model 1.5x faster and burns credits 2-2.5x faster. I ran it live for 36 minutes on a real build. Here is what the trade actually costs you.
Claude Code and Codex, Running on a Local Model for Nothing
I made two videos on this: a live demo and a full crash course. Most people assume Claude Code and Codex only work with a paid plan behind them. They do not. You can wire either one to a model running on your own machine and pay zero per run. Here is the gist of both, enough to s
Claude Code Works in Your Terminal But Fails in Cron
Headless claude -p runs fine by hand and dies with 'Not logged in' from crontab. The cause is the macOS Keychain, and the fix is launchd, not a login.