**Title:** `codex exec` hangs indefinitely mid-run on a dead pooled connection (socket in CLOSE_WAIT, no read timeout fires, no retry) — Windows
What issue are you seeing?
Title: codex exec hangs indefinitely mid-run on a dead pooled connection (socket in CLOSE_WAIT, no read timeout fires, no retry) — Windows
Summary
During a long non-interactive codex exec run, the agent worked normally for ~23 minutes
(steady tool calls and file writes), then stalled completely for 70+ minutes. Inspection
showed the process alive but fully idle, with its only TCP connection to the backend inCLOSE_WAIT: the server had closed the connection, but codex never detected it and kept
waiting for a response that could never arrive. No reconnect was attempted. The documentedstream_idle_timeout_ms default (300000 ms) never fired over 70+ minutes, so the hung code
path appears not to be covered by that timeout.
Environment
- codex-cli 0.142.5 (npm
@openai/codex, latest at time of writing) - Windows 11 Pro 10.0.26100
- Auth: ChatGPT account (subscription), model
gpt-5.5,model_reasoning_effort = "high" - Config: no custom
model_providersentries, no timeout/retry overrides (defaults) windows.sandbox = "unelevated"
Invocation
Prompt piped via stdin (stdin is written once and closed — this is not the
non-TTY-stdin hang of #20919), stdout/stderr redirected to log files:
Get-Content -Raw -Encoding UTF8 prompt.md |
codex -c windows.sandbox="unelevated" --ask-for-approval never exec `
--skip-git-repo-check --sandbox workspace-write --color never --ephemeral `
-o response.md -C <workdir> - > stdout.log 2> stderr.log
Timeline (all times local, same day)
| Time | Observation |
|---|---|
| 06:31:00 | codex process starts (PID 56488) |
| 06:31:01 | single TCP connection opened to backend (Cloudflare edge, 172.64.155.209:443) |
| 06:31:08 | codex spawns its long-lived PowerShell AST-parser helper (normal) |
| 06:31 → 06:54:35 | normal operation: continuous workspace file writes (a new .rs module, ~224 lines added to another, Python edits, test files), 17.28 s CPU accumulated |
| 06:54:35 | last file write — stall begins |
| 08:01 → 08:03 | inspection (see evidence): process alive, CPU delta 0.00 s over 20 s, no new writes, -o response file never created |
| 08:07 | process killed externally after diagnosis; exit code -1; stdout/stderr logs still 0 bytes |
Evidence at hang time (~70 min into the stall)
Get-NetTCPConnection -OwningProcess <pid>:
````
State RemoteAddress RemotePort CreationTime
Bound 0.0.0.0 0 06:31:01
CloseWait 172.64.155.209 443 06:31:01
One connection, in CloseWait since it was the only one ever opened (HTTP/2 reuse for
all requests). The remote peer had sent FIN; codex never closed its side and opened no
replacement connection. No retries, no reconnects.
- Process CPU: 17.28 s total; delta 0.00 s over a 20 s sample.
- Children: only the PowerShell AST-parser helper daemon (idle; expected).
- stdout/stderr logs: 0 bytes (also 0 during the healthy phase, so stderr progress appears
buffered when redirected — makes external liveness detection harder).
Expected behavior
Either of:
- The idle/read timeout (
stream_idle_timeout_ms, default 300000 ms) aborts the dead read
within ~5 minutes and the existing retry logic (stream_max_retries /
request_max_retries) reconnects; or
- the connection pool detects/validates the half-closed connection (e.g. HTTP/2 keepalive
PING with a timeout, or pool max-idle eviction) before reusing it.
Actual behavior
The process waits indefinitely (70+ minutes observed here; a run with the same signature —
process alive, 0 CPU delta, 0 bytes output, -o file never written — was also seen on an
earlier version on 2026-06-11, so this is recurring). Anecdotally such runs sometimes
"self-recover" after roughly 2 hours, which matches the Windows TCP keepalive default
(7200 s) finally erroring the socket rather than any application-level timeout.
Hypothesis
The stall point appears to be a request issued on a reused pooled HTTP/2 connection that
the server had already closed: writing the request succeeds locally, and codex then waits
for response headers on a path that is not guarded by stream_idle_timeout_ms (which seems
to apply to active SSE reads, not to the header/connect phase of a pooled connection).
Suggested fixes
- Apply an overall per-request timeout that also covers the response-header wait on reused
connections; feed failures into the existing retry logic.
- Enable HTTP/2 keepalive PINGs (with timeout) and/or a pool max-idle age so half-closed
connections are evicted instead of reused.
- Consider unbuffered/line-buffered stderr progress when stdio is redirected, so external
supervisors can detect liveness.
Workaround we use
An external watchdog: kill the codex process when CPU delta ≈ 0 and no workspace/response
activity occurs for N minutes, then re-dispatch the run. Works, but wastes the partial run
and requires babysitting infrastructure.
What steps can reproduce the bug?
Timeline (all times local, same day)
| Time | Observation |
|---|---|
| 06:31:00 | codex process starts (PID 56488) |
| 06:31:01 | single TCP connection opened to backend (Cloudflare edge, 172.64.155.209:443) |
| 06:31:08 | codex spawns its long-lived PowerShell AST-parser helper (normal) |
| 06:31 → 06:54:35 | normal operation: continuous workspace file writes (a new .rs module, ~224 lines added to another, Python edits, test files), 17.28 s CPU accumulated |
| 06:54:35 | last file write — stall begins |
| 08:01 → 08:03 | inspection (see evidence): process alive, CPU delta 0.00 s over 20 s, no new writes, -o response file never created |
| 08:07 | process killed externally after diagnosis; exit code -1; stdout/stderr logs still 0 bytes |
Evidence at hang time (~70 min into the stall)
Get-NetTCPConnection -OwningProcess <pid>:
````
State RemoteAddress RemotePort CreationTime
Bound 0.0.0.0 0 06:31:01
CloseWait 172.64.155.209 443 06:31:01
One connection, in CloseWait since it was the only one ever opened (HTTP/2 reuse for
all requests). The remote peer had sent FIN; codex never closed its side and opened no
replacement connection. No retries, no reconnects.
- Process CPU: 17.28 s total; delta 0.00 s over a 20 s sample.
- Children: only the PowerShell AST-parser helper daemon (idle; expected).
- stdout/stderr logs: 0 bytes (also 0 during the healthy phase, so stderr progress appears
buffered when redirected — makes external liveness detection harder).
What is the expected behavior?
Expected behavior
Either of:
- The idle/read timeout (
stream_idle_timeout_ms, default 300000 ms) aborts the dead read
within ~5 minutes and the existing retry logic (stream_max_retries /
request_max_retries) reconnects; or
- the connection pool detects/validates the half-closed connection (e.g. HTTP/2 keepalive
PING with a timeout, or pool max-idle eviction) before reusing it.
Additional information
_No response_
This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗