Session task panics can leave turns without TurnComplete
Summary
While investigating a stuck app-server runner in an external WebSocket client, I found a Codex lifecycle failure mode where a panicking SessionTask::run() can leave the turn without a TurnComplete event.
The process and WebSocket can remain alive, but clients that wait for the semantic turn lifecycle never observe completion and keep the task running indefinitely.
Root cause
In codex-rs/core/src/tasks/mod.rs, Session::start_task spawns an async task and awaits task_for_run.run(...) directly. If the task future panics, execution exits the spawned task before the cleanup path runs:
flush_rollout()is skippedon_task_finished(...)is skipped- waiters are not notified
- no
TurnCompleteis emitted - the active turn can remain stuck from the client perspective
Impact
This is especially visible for app-server/WebSocket clients that treat TurnComplete as the authoritative completion signal. Transport-level heartbeat/ping/pong can still be healthy, so this does not look like a dead process or broken socket.
I do not want to overclaim that every externally observed stuck turn is caused by this path, but this is a concrete lifecycle hole that can produce the same class of symptom.
Proposed patch
I prepared a small patch in my fork:
https://github.com/dyjxg4xygary/codex/commit/c51d3c56f0efdcbbf494b5a65e4309a8410d99c5
Compare link:
https://github.com/openai/codex/compare/main...dyjxg4xygary:fix/websocket-semantic-idle?expand=1
The patch wraps SessionTask::run() with catch_unwind(). On panic it emits an internal error event, then continues through the existing finish path so rollout flush, task finish handling, waiter notification, and TurnComplete still happen.
I could not open a PR because this repository currently limits PR creation to collaborators.
Regression test
The patch adds panicking_task_emits_error_and_completes_turn, which creates a synthetic SessionTask that panics in run() and asserts that Codex emits:
EventMsg::ErrorwithCodexErrorInfo::InternalServerErrorEventMsg::TurnComplete- a cleared
active_turn
Verification
Targeted tests passed locally:
CARGO_NET_GIT_FETCH_WITH_CLI=true cargo test -p codex-core session::tests::panicking_task_emits_error_and_completes_turn
CARGO_NET_GIT_FETCH_WITH_CLI=true cargo test -p codex-core session::tests::spawn_task_turn_span_inherits_dispatch_trace_context
just fmt
git diff --check
I also ran the full cargo test -p codex-core. It completed with unrelated local failures in existing agent init / shell snapshot / unified exec timing tests: 1617 passed; 9 failed; 3 ignored.