CLI reuses dead Responses WebSocket after network loss during idle, breaking the next turn
What version of Codex CLI is running?
codex-cli 0.144.4
The same liveness logic is still present on the current main branch as of 2026-07-15.
What subscription do you have?
ChatGPT account authentication. The specific subscription tier does not appear material to this transport-liveness failure.
Which model were you using?
gpt-5.6-sol
What platform is your computer?
Linux 6.8.0-124-generic x86_64 x86_64
Ubuntu 22.04, npm installation.
What terminal emulator and version are you using (if applicable)?
VTE 6800, TERM=xterm-256color. No terminal multiplexer was involved.
Codex doctor report
A full report is not included because the available diagnostic run occurred inside a network-restricted harness after applying a local prototype patch, so its reachability result is not representative of the original failure.
Relevant redacted fields:
{
"schemaVersion": 1,
"codexVersion": "0.144.4",
"authMode": "chatgpt",
"model": "gpt-5.6-sol",
"modelProvider": "openai",
"proxyEnvVars": "none",
"platform": "linux-x86_64",
"installMethod": "npm"
}
What issue are you seeing?
An already-open interactive CLI chat can stop continuing normally after this sequence:
- A turn completes.
- The chat remains open but idle.
- Internet connectivity is interrupted long enough for the Responses WebSocket pump to exit.
- Connectivity returns before the user sends another prompt.
- The next prompt is sent in the same open chat.
This is not about codex resume, restoring a process, or reconnecting during an active streamed response. The process and TUI remain open throughout; the failure occurs on the first ordinary turn after an idle network interruption.
The cached cross-turn Responses WebSocket is treated as live even though its background pump task has already terminated. The next turn therefore attempts to reuse a dead connection and can fail instead of transparently opening a new connection and continuing with the existing conversation history.
The source-level failure path appears to be:
ModelClientSessioncaches aResponsesWebsocketConnectionacross turns.WsStreamowns a command channel and a backgroundpump_task.- The pump exits when the idle socket encounters a transport error/EOF.
ResponsesWebsocketConnection::is_closed()currently only checks whetherstream: Option<WsStream>isNone.- A finished pump can remain stored as
Some(WsStream), so the cached connection is incorrectly reported as open.
Current implementation:
pub async fn is_closed(&self) -> bool {
self.stream.lock().await.is_none()
}
What steps can reproduce the bug?
- Start an interactive Codex CLI session using the Responses WebSocket transport.
- Send a prompt and wait until the turn completes.
- Leave the CLI open and idle.
- Disconnect the machine from the internet long enough for the underlying WebSocket pump to observe EOF or a transport error and exit.
- Restore internet connectivity without closing or resuming the Codex process.
- Send another prompt in the same open chat.
- Observe that the next turn can fail/break continuity because the cached dead WebSocket is reused.
A deterministic regression test can avoid manipulating the host network:
- Script a WebSocket server to accept the first connection and complete one response.
- Close that connection while the client is idle.
- Wait for the client pump task to finish.
- Submit a second turn through the same
ModelClientSession. - Assert that the client creates a second WebSocket connection.
- Assert that the new request does not reuse a
previous_response_idtied to the dead connection and instead carries the full prompt history needed to continue the conversation.
What is the expected behavior?
When the background WebSocket pump has exited, the cached connection should be considered closed.
The next prompt in the still-open chat should transparently establish a fresh Responses WebSocket and continue the same conversation using the existing retry/full-history path. The user should not need to restart or resume the session.
Additional information
A minimal fix is to include pump/channel liveness in the cached-connection check:
impl WsStream {
fn is_closed(&self) -> bool {
self.tx_command.is_closed() || self.pump_task.is_finished()
}
}
pub async fn is_closed(&self) -> bool {
self.stream
.lock()
.await
.as_ref()
.is_none_or(WsStream::is_closed)
}
A local prototype with this change compiles successfully as a 0.144.4 release and passes CLI --version / --help smoke checks. A regression test was also prepared for the two-turn/two-connection behavior, but the test suite was not run locally.
Duplicate searches performed before filing included:
- “websocket idle internet disconnect conversation continuity”
- “network disconnect inactivity stale websocket next turn”
- “chat session breaks after internet reconnect idle”
- source identifiers
ResponsesWebsocketConnection,pump_task, andis_closed
No matching issue or pull request was found. Related reports about remote-control enrollment, app-server client state, active-stream disconnect loops, or IPv6 handshake fallback concern different transports or failure timing.
2 Comments
I independently reproduced this on Codex Desktop/CLI
0.146.0-alpha.9.2on Windows.The observed sequence matches this report:
tx_commandfailed withConnection closed normallyI verified that current
mainatbb5054fe47abe73ecbbd454751066a28c89f4bb9still has the stale liveness check and prepared the minimal patch described above:WsStream::is_closed()checks bothtx_command.is_closed()andpump_task.is_finished()ResponsesWebsocketConnection::is_closed()delegates to that stream-level checkPatch commit: https://github.com/i69699/codex/commit/d6a407a69d5eaba0bd40e5e17ec69f1431d84d7a
Validation on Windows:
cargo +stable nextest run --no-fail-fast -p codex-api: 158/158 passedcargo +stable clippy --fix --tests --allow-dirty -p codex-api: passed with no warningsThe branch is ready if a maintainer would like to invite a PR.
I implemented and tested a broader version of the proposed fix.
Proposed branch: https://github.com/bombaris34/codex/tree/agent/fix-websocket-reconnection-fallback
Commit: https://github.com/bombaris34/codex/commit/66166d182bf9421eab5479ff880b98eec2e61735
In addition to treating a closed command channel or finished pump task as a closed cached connection, the patch:
response.failederrors;transport_failurefield to existing retry logs.It adds no heartbeat, probe, or other steady-state network traffic.
Validation:
just test -p codex-protocol: 273 passedjust test -p codex-api: 158 passedcodex-protocol,codex-api, andcodex-corepassedjust fmtpassedThis is also relevant to the intermittent reconnect symptoms reported in #37649. GitHub currently prevents this account from opening a PR because external contributions are invitation-only, but the branch is ready for a maintainer to review or invite as a PR.