codex-cli 0.92.0 panics on Broken pipe when stdout closes during --json streaming (macOS)
Summary
On macOS, codex-cli 0.92.0 can crash mid-run with a Rust panic:
failed printing to stdout: Broken pipe (os error 32)
We started seeing this only after upgrading to 0.92.0. It shows up most often in headless/detached runners where codex exec --json is piped (e.g. via tee) and the stdout consumer closes.
Environment
- codex-cli: 0.92.0
- macOS: 15.5 (24F74)
- Auth: API key (not ChatGPT login)
Observed error
From codex.stderr.log:
thread 'main' (...) panicked at library/std/src/io/stdio.rs:1165:9:
failed printing to stdout: Broken pipe (os error 32)
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
How we trigger it
In our harness we were doing something like:
codex exec --json ... | tee events.jsonl >/dev/null
In headless/detached contexts, if the stdout consumer closes (runner exits, HUP, etc.), codex may panic with Broken pipe and terminate the run.
Minimal repro idea
This should not crash; it should exit gracefully if the consumer closes:
# Any prompt; keep it tiny to minimize cost.
codex exec --skip-git-repo-check --json "Print OK" | head -n 1 >/dev/null
(If head closes early, upstream should handle EPIPE without panicking.)
Workarounds
- Redirect stdout directly to a file instead of piping:
codex exec --json ... > events.jsonl 2> codex.stderr.log
- Pinning to
codex-cli 0.91.0avoids the issue for us in headless runs.
Requested behavior
Please handle EPIPE/BrokenPipe on stdout gracefully (no panic) when streaming --json.
Happy to provide more logs or try a debug build/backtrace if you point me at what to collect.
5 Comments
Additional datapoint: in our environment, redirecting stdout to a file (avoiding
| tee) improved things but did not fully eliminate mid-run failures in headless execution. The only change that made headless runs consistently reliable for us was pinning tocodex-cli 0.91.0(running the pinned CLI in parallel and using it for headless runs).So: stdout EPIPE handling seems like one culprit, but there may be another regression in 0.92.0 affecting long/headless runs. Happy to run any additional diagnostics or try specific flags/builds if useful.
Thanks for the bug report. I think I understand the root cause, but it's a long-standing issue. I'm not 100% sure why you saw this first in 0.92. I have a theory that a recently-introduced warning may have exacerbated the problem. Do you happen to have any feature flags enabled in your config.toml that correspond to "under-development features"? If you launch
codexin normal interactive (TUI) mode, do you see a warning printed?Checked feature flags + quick experiment:
~/.codex/config.tomlhas both under-development flags enabled:[features] unified_exec = true[features] shell_snapshot = truecodex exec --json "echo OK" | head -n 1codex --config "features.unified_exec=false" --config "features.shell_snapshot=false" exec --json "echo OK" | head -n 1Both runs exit rc=0 but print the same panic to stderr (
failed printing to stdout: Broken pipe).Also: in non-interactive
codex execruns I don’t see any warning printed to stderr at startup (stderr is empty unless an error/panic occurs). Happy to run additional probes if you have a preferred build/flag combo to try.And just to confirm, when you downgrade to 0.91.0, the problem goes away? I just want to verify that it's a Codex regression rather than some other recent change in your environment or configuration.
I can still reproduce this on a newer macOS release build.
Environment:
Minimal repro:
Observed output:
I noticed this issue already has a linked fix commit and PR (#10259), but the current 0.130.0 npm release I have installed still panics in this path. The repro above also uses
--ignore-user-config --disable plugins, so it does not appear to depend on plugin initialization.For reference, current
mainstill shows the JSONL path writing throughprintln!incodex-rs/exec/src/event_processor_with_jsonl_output.rs,EventProcessorWithJsonOutput::emit, lines 103-115 in the GitHub view I checked. That still looks like the place where an early downstream close fromheadcan turn EPIPE into a Rust stdout panic instead of a graceful shutdown.