codex exec "<prompt>" hangs indefinitely when stdin is a non-TTY pipe with no writer

Open 💬 3 comments Opened May 4, 2026 by alexanderaperry-arch
💡 Likely answer: A maintainer (github-actions[bot], contributor) responded on this thread — see the highlighted reply below.

Summary
When codex exec is invoked with a prompt as an argument from a non-interactive child shell (e.g. a CI runner, an editor's task runner, or another agent that spawns child processes), it hangs forever waiting for stdin EOF — even though the prompt was passed via the positional argument and stdin is never used.

This is a deterministic deadlock, not a flake. Reproducible on Windows 11 with codex-cli 0.128.0 and prior versions.

Repro

Inside a non-interactive child shell (here: a parent process spawning powershell.exe with piped stdio):

& codex exec "Reply OK only."

Hangs. Process has 0% CPU. No docker.exe MCP child spawned. No rollout

file written under ~/.codex/sessions. Stays hung until manually killed.

The same call with stdin redirected from NUL completes in ~11 s with normal output:

cmd /c "codex exec "Reply OK only." < NUL"

OK

tokens used: 19,542

Diagnosis
Stack inspection (no MCP child, 0 CPU, no rollout output, only stderr line Reading additional input from stdin...) plus --help text confirm that exec reads stdin when it is detected as a non-TTY pipe and appends the contents as a <stdin> block after the prompt argument:

If stdin is piped and a prompt is also provided, stdin is appended as a <stdin> block

In a non-TTY child shell with inherited but unwritten stdio, read() on stdin blocks indefinitely because the writer side is open but no bytes ever arrive and no EOF is delivered.

Why this matters
Standard for tools that spawn codex exec programmatically (Claude Code, GitHub Actions runners, nohup, Start-Process without -RedirectStandardInput NUL, agent orchestrators).
The codex app-server path (used by the Claude Code Codex plugin) is unaffected because the plugin actively writes JSONL and closes stdin.
Indistinguishable from a real hang/network problem from the caller's perspective — no logs, no rollout, no exit code, just silence.
Common mitigations (timeouts, retries) make the symptom worse: the next retry hangs the same way.
Proposed fix (one of)
When a prompt argument is present and stdin is a non-TTY pipe, only read stdin if data is actually available within a short timeout (e.g. poll for readability for 200 ms; if nothing, treat as empty and proceed).
Add an explicit --no-stdin flag (or invert the default to require - for stdin reads), so non-interactive callers can opt out cleanly.
Document the failure mode and the workaround prominently in codex exec --help, and emit a stderr warning after, say, 5 s of waiting so the deadlock is at least detectable.
Option 1 is the friendliest default and matches what most callers expect when they pass a prompt argument. Option 2 is the smallest behavior change.

Workaround (current)
POSIX shells: codex exec "<prompt>" < /dev/null
Windows cmd: codex exec "<prompt>" < NUL
Windows PowerShell child: cmd /c "codex exec "<prompt>" < NUL" (PowerShell has no clean pipe-to-NUL syntax)
Or wrap once: a codex-safe.cmd shim that forwards args and redirects from NUL.
Environment
Windows 11 26200 (Home)
codex-cli 0.128.0 (npm install path: C:\Users\<u>\AppData\Roaming\npm\codex.cmd → node_modules\@openai\codex-win32-x64\vendor\x86_64-pc-windows-msvc\codex\codex.exe)
Reproduces from PowerShell child invoked by Claude Code's Bash tool (PowerShell runs & codex exec "<prompt>" with inherited piped stdio)
Also reproduces from Start-Process powershell -ArgumentList ... without -RedirectStandardInput NUL

View original on GitHub ↗

3 Comments

github-actions[bot] contributor · 3 months ago

Potential duplicates detected. Please review them and close your issue if it is a duplicate.

  • #19945

Powered by Codex Action

mitre88 · 3 months ago

I validated this on current main and prepared a minimal branch: https://github.com/mitre88/codex/tree/fix/exec-optional-stdin-ready-check

The change keeps forced stdin behavior unchanged (codex exec - and no positional prompt still read stdin). When a positional prompt is present, optional stdin append now waits up to 200 ms for pipe data and proceeds without a <stdin> block if none arrives, matching the suggested option 1. It still appends immediately available piped input.

Added coverage:

  • Unix unit tests for an empty open pipe and a pipe with bytes.
  • An integration test that passes codex-exec an open stdin pipe with no data and asserts the request contains only the positional prompt.

Validation:

  • cargo fmt --package codex-exec --check
  • git diff --check
  • cargo test -p codex-exec stdin_fd_has_data_within -- --nocapture
  • cargo test -p codex-exec exec_with_prompt_does_not_wait_for_empty_open_stdin_pipe --test all -- --nocapture
  • cargo test -p codex-exec

I also tried cargo check --target x86_64-pc-windows-msvc -p codex-exec, but this local machine does not have that Rust target installed (can't find crate for core). I can open the PR if maintainers want this branch.

mitre88 · 3 months ago

I refreshed the branch against current main and revalidated the focused coverage.

Updated branch: https://github.com/mitre88/codex/tree/fix/exec-optional-stdin-ready-check
Compare: https://github.com/openai/codex/compare/main...mitre88:codex:fix/exec-optional-stdin-ready-check

Validation after rebase:

  • cargo fmt --package codex-exec --check
  • cargo test -p codex-exec stdin_fd_has_data_within -- --nocapture
  • cargo test -p codex-exec exec_with_prompt_does_not_wait_for_empty_open_stdin_pipe --test all -- --nocapture
  • git diff --check

The scope is still the same: forced stdin remains unchanged, while optional stdin alongside a positional prompt only appends when data is ready.