Codex CLI Hangs at "Reading additional input from stdin..." in Scripts: Why and the One-Line Fix

If you call OpenAI's Codex CLI from a script and it sits there printing only this, then nothing until your timeout fires:

Reading additional input from stdin...

the fix is one redirect: < /dev/null. Here's why it happens, how to reproduce it in ten seconds, and the equivalent fix for Python, Node.js and systemd. (Tested with codex-cli 0.160.0 on Linux.)

Where I ran into it

On my home server, several AI agents can call each other. One of them (Claude Code) runs shell commands in the background and calls codex exec for second opinions and code reviews. The first time it did that from a background job, the call never finished. It printed the line above and hung until the job timed out, with no error and no output.

Running the exact same command by hand in a terminal worked instantly. That's the clue: the difference is what stdin is connected to.

Why it waits

codex exec --help explains it, if you read the description of the prompt argument closely:

If not provided as an argument (or if - is used), instructions are read from stdin. If stdin is piped and a prompt is also provided, stdin is appended as a <stdin> block.

So even when you pass the prompt as an argument, if stdin is not a terminal, Codex assumes you're piping extra context in, and it reads stdin until end-of-file before starting. In an interactive terminal, stdin is a TTY, so it doesn't wait. In a script, stdin is whatever the parent process handed down, and it's often a pipe that never closes:

  • a background job started by another program that keeps its own end of the pipe open,
  • an agent or tool runner that talks to child processes over pipes,
  • a parent script that is itself being fed by a long-running pipe.

Codex waits for an EOF that never comes.

Reproduce it

sleep 60 | … gives Codex a stdin pipe that stays open for a minute without ever sending data, which is exactly the background-job situation:

# stdin is a pipe that never closes: codex waits for EOF until the timeout kills it
$ sleep 60 | timeout 45 codex exec --skip-git-repo-check "Reply with just OK"
Reading additional input from stdin...
$ echo $?            # in bash: echo ${PIPESTATUS[1]}
124

# stdin explicitly empty: done in seconds
$ codex exec --skip-git-repo-check "Reply with just OK" < /dev/null
...
OK

Exit code 124 is timeout killing it. With < /dev/null, stdin is empty and closed from the start, so Codex reads nothing, sees EOF immediately, and runs. In my test it finished in about 8 seconds.

The fix, everywhere

Give Codex an explicitly empty stdin whenever you aren't deliberately piping something in:

# Shell scripts, cron jobs, CI steps
codex exec "Summarize the failing tests" < /dev/null

# Python
import subprocess
subprocess.run(["codex", "exec", "Summarize the failing tests"],
               stdin=subprocess.DEVNULL, capture_output=True, text=True, timeout=600)

# Node.js
const { spawnSync } = require("child_process");
spawnSync("codex", ["exec", "Summarize the failing tests"],
          { stdio: ["ignore", "pipe", "pipe"], timeout: 600000 });

# systemd: StandardInput defaults to null, so a service is already safe

I tested the shell and Python versions. The Node.js version uses "ignore", which connects the child's stdin to /dev/null. Two notes:

  • Python's default is "inherit", so whether subprocess.run() hangs depends entirely on what your script's stdin is. It may work from your terminal and hang under a job runner. stdin=subprocess.DEVNULL makes it deterministic.
  • Always set a timeout as well. An agent call can legitimately take minutes, but it should never be allowed to hang forever.

It's a feature when the pipe closes

The behavior that bites you in background jobs is useful on the command line. When the pipe does end, the piped text arrives as context:

# The same behavior is a feature when the pipe does close:
$ git diff | codex exec "Review this diff for bugs"
$ echo "banana" | codex exec "Reply with only the word inside the stdin block"
banana

So the rule is simple: if you pipe something in, make sure the pipe ends. If you don't mean to pipe anything, redirect from /dev/null.

Related gotcha: approvals in the background

The same "nobody is at the keyboard" problem shows up in a second way. Another agent on the same server (Hermes) asks for approval before running certain commands. When I called it from a background job, the approval request timed out with no one to answer it, the command was denied, and the task stalled. When you call any agent unattended, check what it does when it would normally ask a human. For Codex reviews I add --sandbox read-only, so an unattended call can't change anything even if the prompt goes wrong.

Summary

  • Reading additional input from stdin... means Codex is waiting for EOF on a piped stdin.
  • It happens when stdin isn't a TTY and the pipe never closes, which is typical of background jobs and tool runners.
  • Fix: < /dev/null, stdin=subprocess.DEVNULL, or stdio: ["ignore", …]. systemd services are already safe.
  • Piping real input (git diff | codex exec "…") is the intended use, and it works because that pipe closes.

Comments

Popular posts from this blog

Gear Ratio Calculator: Multi-Stage Ratio, Output Speed, Torque and Direction

Mecanum-Wheel Pick-and-Place Robot: From Arduino UNO to Mega2560 + ESP32-CAM

Voltage Divider Calculator: Output Voltage, or the Best E24 Resistor Pair for a Target Voltage