codex exec --json hangs indefinitely when --image flag is used
Resolved 💬 11 comments Opened Oct 26, 2025 by jet-ds Closed Oct 28, 2025
💡 Likely answer: A maintainer (github-actions[bot], contributor)
responded on this thread — see the highlighted reply below.
Bug Description
codex exec --json hangs indefinitely when the --image flag is used. The CLI successfully initializes (thread.started event is emitted) but then never processes the request or emits any further events.
Environment
- Codex CLI Version: 0.50.0
- Platform: macOS (Darwin 25.0.0)
- Installation: npm global install (
npm i -g @openai/codex)
Minimal Reproduction
# Create or use any test image
echo "Describe what you see in this image" | codex exec --json --image /path/to/any/image.png
Expected behavior:
- Codex should process the image and prompt
- Stream events like
item.*,agent_message_delta,agent_message, etc. - Eventually emit
turn.completedortask_complete
Actual behavior:
- Emits
{"type":"thread.started","thread_id":"..."} - Hangs indefinitely - no further output
- Never completes, must be killed manually
Without Images: Works Fine
echo "Write a hello world script" | codex exec --json --cd /tmp
This works perfectly and streams all expected events (item.completed, agent_message, etc.).
Additional Context
- The bug exists in both 0.49.0 and 0.50.0
- The hang occurs after initialization, suggesting a state machine bug in the event loop
- The TypeScript SDK (
@openai/codex-sdk) is also affected since it uses--imageflags internally - No errors are emitted to stderr
- The process doesn't crash; it just stops producing output
Workaround
Currently using a timeout mechanism to detect the hang:
try:
line = await asyncio.wait_for(
process.stdout.readline(), timeout=90
)
except asyncio.TimeoutError:
# Kill and report error
process.kill()
Impact
This makes image-based workflows completely unusable in non-interactive mode, which is critical for CI/CD pipelines and programmatic integrations.
Related Issues
This may be related to:
- #2473 (codex-cli vision capability issues)
- #1619 (image processing limitations)
11 Comments
Potential duplicates detected. Please review them and close your issue if it is a duplicate.
Powered by Codex Action
This is NOT a duplicate of #4946.
Different Bug, Different Symptoms
Issue #4946:
This issue (#5773):
thread.startedthen produces no output whatsoeverTest Case Difference
#4946 involves unsupported formats:
This issue (#5773) involves supported formats:
Root Cause Difference
These are distinct bugs in different parts of the Codex CLI codebase.
2025-10-21T00:42:17.364881Z DEBUG ChatComposer::handle_paste_image_path próba preview=11;rgb:1818/1818/1818
<img width="1069" height="270" alt="Image" src="https://github.com/user-attachments/assets/2f7b4ca1-2b06-4421-a751-05e34053b86b" />
Url
I described the problem here — it seems that in this case it could also be the cause. You can see in the logs what’s happening with the image.
#5683
Thanks for the reference @AdsQnn! I dug into the source code to check if these are related.
Code Analysis
You're right that there's a connection - both interactive paste and
exec --imageuse the same underlying code path:Interactive mode (
tui/src/chatwidget.rs:1339-1344):Exec mode (
exec/src/lib.rs:324-328):Both create
UserInput::LocalImage { path }and submit viaOp::UserInput.Why Exec Hangs Specifically
The exec mode hang occurs here (
exec/src/lib.rs:330-341):Exec waits for
TaskCompletebefore sending the text prompt. If that event is never emitted, it hangs forever.Relationship to #5683
The issues might share the same buggy image handling subsystem, but manifest differently:
I'll keep this issue open since:
TaskCompletefor images)But you're right to flag the connection - there may be deeper image handling bugs that affect multiple code paths.
Okay, but the whole point is that I never pasted any image at all. The terminal is injecting it on its own. So I suspect that in both modes, the terminal picks up the color or the presence of “/” as if it were an image — one that doesn’t actually exist — and that’s why it never returns a response and ends up freezing.
And now, when you launch Codex with
--image, it immediately mounts//rgbin the paste terminal, which is detected as an image paste. I think that might be what’s causing the lag. We’ll see if it kicks in and works :)It’s simply that under the hood,
//rgbgets injected into the input — something you can’t see as a user without checking the logs. It goes straight into the paste buffer.Thanks for the detailed investigation @AdsQnn! Your fix for the color query bug in #5683 is really valuable. I wanted to understand how it might relate to this exec mode hang, so I dug into the code a bit.
What I Found in Exec Mode
I checked the exec implementation and it seems to work differently from interactive mode:
Exec source (
codex-rs/exec/src/):crosstermortuiecho "text" | codex exec) rather than terminalis_terminal()to validate stdin typeWhen I tested:
Output:
The Wait Loop
I found exec waits for a
TaskCompleteevent after submitting images (exec/src/lib.rs:330-341):That event never arrives, so it hangs.
My Take
I think the color query issue affects interactive mode specifically, while exec has a different manifestation of buggy image handling - maybe something in the shared
UserInput::LocalImageprocessing that fails to complete properly?But I could be missing something about how terminal I/O works in exec. What do you think?
Could be :) I didn’t check it that thoroughly — I just wrote it because it seemed to me there might be some connection. But since you’ve checked and it doesn’t trigger anything, then yeah, it’s probably just as you said :)
What matters is that you managed to find the problem — nice!
---
Two Consumers of the Same Event Stream - Race Condition Analysis
The Problem: Race Condition
Two consumers are competing for the same event stream:
conversation.next_event()→ sends to channelrxconversation.next_event()→ waits directlyWhen
TaskCompletearrives:conversation.next_event()first gets the eventWhy the Fix Works
Single source of truth - all events flow through the channel
rx:Every event passes through the background thread → channel
tx→ main thread receives fromrx. Zero race conditions, everything deterministic.---
Code - How It Is (Buggy)
Code - How It Should Be (Fixed)
Key Difference
Instead of
conversation.next_event(), we userx.recv()- all events flow through a single channel, eliminating the race condition entirely.I thought I'd take a quick look while I'm at it :)
Thanks so much @AdsQnn for finding the race condition! That's exactly the issue - I verified that conversation.clone() creates competing receivers on the same async_channel. Really clean diagnosis.
Are you planning to submit a PR with this fix? If not, I'd be happy to help, but wanted to defer to you since you diagnosed it. :)
You found it — I just gave a little hint :) Go ahead and send the fix if you want :)
Thanks again for the detailed diagnosis\! I've submitted the fix in #5814. Really appreciate you taking the time to track down the race condition—saved me a lot of debugging time.
@jet-ds, I asked a colleague to review your PR. He's more familiar with this code than I am. He suggested an alternate fix that results in simpler code. I've posted a PR here if you'd like to review.