Windows desktop thread can stop returning local shell command results; even small local file reads hang until manually aborted
What version of the Codex App are you using (From “About Codex” dialog)?
Unknown. I did not capture the exact app version before reporting because the affected thread could no longer reliably return command output.
What subscription do you have?
Unknown / not relevant to the reproduction.
What platform is your computer?
Windows desktop app. Workspace path involved was on G:\cursor project\codex.
What issue are you seeing?
A Codex Desktop thread can get into a bad state where very small local shell commands never return results to the thread.
This does not look like a normal network failure, because the affected commands were local file reads that should complete immediately, for example:
Get-Content -Raw .\\AGENTS.mdcmd /c type AGENTS.md- other small local inspection commands
Observed behavior:
- The command appears to start but no result comes back to the thread.
- After waiting a long time, the only visible outcome is effectively that the command had to be manually interrupted/aborted.
- The issue persisted even after switching from parallel calls to a single minimal command.
read_thread_terminalalso indicated that no app terminal session was attached to the thread yet.
This makes the problem look more like a thread/session/tool-return-path failure than a bad shell command.
What steps can reproduce the bug?
I cannot guarantee a 100% minimal repro yet, but this was the sequence that triggered it for me:
- Open Codex Desktop on Windows.
- Work in a local workspace.
- Run a few tool/shell commands in the thread, including at least one interrupted or aborted command.
- Continue trying small local shell commands in the same thread.
- Eventually even trivial local commands such as reading a small local file stop returning output.
What is the expected behavior?
Small local shell commands should return quickly in the thread, especially simple local file reads that do not require network access.
If a thread loses its terminal/session attachment, the app should surface a clear error and recover cleanly instead of appearing to hang.
Additional information
A few reasons this seems worth triaging as a desktop-thread/session issue:
- It affected local file reads, so it does not look like an internet connectivity issue.
- It still happened after simplifying the command down to a single-file read.
- It happened within a specific thread/session rather than looking like a deterministic command syntax problem.
- It may be related to thread state becoming dirty after multiple manual aborts/interrupted commands.
I also considered whether this could be a Codex CLI conflict, but from the observed behavior it looks more like a desktop app thread/session regression or a tool result handoff issue.
If needed, I can try again in a fresh thread and provide follow-up data comparing:
- old thread vs new thread behavior
- after app restart vs before app restart
- whether the problem reproduces only after several manual aborts
6 Comments
Additional data point from the reporter:
This seems to strengthen the hypothesis that the issue may be related to thread/session state getting stuck or corrupted, rather than a permanent workspace/file problem.
If you're able to repro, please use
/feedbackto upload the logs and session details, then post the thread ID here.Potential duplicates detected. Please review them and close your issue if it is a duplicate.
Powered by Codex Action
Diagnosed and verified the root cause and fix for this issue:
This hang is caused by a concurrency deadlock in the shell execution pipeline due to back-pressure on a bounded async event channel.
When a command like Get-Content -Raw produces a massive burst of text, the read_output background task in codex-rs/core/src/exec.rs reads it in chunks and emits UI delta events via a bounded channel (stream.tx_event.send(event).await).
If the UI/agent loop cannot consume these events fast enough, the channel fills up, and the send().await call blocks the read_output task. Because the Rust stream reader is suspended, the OS process pipe buffer (~64KB on Windows) fills up, causing the child process (PowerShell) to block indefinitely trying to write to stdout. The main agent loop is then stuck waiting on child.wait(), resulting in a permanent "Working..." state.
The Fix:
Replace the blocking .await call with a non-blocking .try_send() when emitting streaming UI events (ExecCommandOutputDeltaEvent).
1 // codex-rs/core/src/exec.rs (Inside read_output)
2 // Change:
3 // let _ = stream.tx_event.send(event).await;
4 // To:
5 let _ = stream.tx_event.try_send(event);
This allows the stream reader to intentionally drop intermediate UI updates when under back-pressure, keeping the pipe drained.
The actual process output is still independently aggregated in memory without back-pressure and successfully returned at the end of the tool call.
I have compiled and verified this fix locally, and it completely resolves the hanging behavior for large file reads on Windows.
Since external PRs are by invitation only, I'm providing the fix here.
(I had raised a separate issue (https://github.com/openai/codex/issues/18983), but have closed that as it does appear to be duplicate of this one)
@dzammit, thanks for digging into this. I don't think this is the root cause.
In current main, the
stream.tx_eventchannel used byread_outputis created withasync_channel::unbounded(), sostream.tx_event.send(event).awaitshould not block from normal channel back-pressure. Because of that, changing it totry_send()would not actually drop intermediate deltas under load in the current code; it would still enqueue them unless the receiver is closed.Hi @etraut-openai - Apologies, you're right, thanks for checking this.
Further investigation confirmed that
stream.tx_eventis backed byasync_channel::unbounded(), sostream.tx_event.send(event).awaitis not the bounded back-pressure point intially described. Changing that call totry_send()was not the right fix.The other place that may still introduce bounded back-pressure before event delivery is rollout persistence:
send_event_rawpersists rollout items before callingdeliver_event_rawpersist_rollout_itemscalls intoRolloutRecorder::record_itemsRolloutRecorderuses a bounded Tokiompsc::channel::<RolloutCmd>(256)record_itemsusesself.tx.send(...).awaitfor high-volume output deltas, that could block before the UI event is deliveredSo, the better candidate for this issue seems to be the rollout recorder queue, specifically making normal
RolloutCmd::AddItemsenqueueing non-blocking while keeping explicitpersist/flush/shutdownoperations reliable.The earlier
exec.rschange did appear to improve the repro locally but that may have just been a timing/scheduling effect. Removing a.awaitfrom the stdout-reading loop can make the reader drain output faster even on an unbounded channel, which may have resulted in avoiding the timing-sensitive hang even if it wasn't addressing the actual back-pressure point.In any event, I'll do some of my own checks on this and update feedback/findings here.