Python SDK can drop an early turn/completed notification
Summary
The Python SDK can wait indefinitely when a server sends turn/* notifications before the turn/start response. In particular, an early turn/completed notification is discarded before the response registers the turn queue.
Reproduction
Use a server or transport that emits these notifications while the turn/start request is still awaiting its response:
turn/startedor a turn delta for the new turn.turn/completedfor that same turn.- Return the successful
turn/startresponse.
Expected: after turn_start returns, both notifications are available to the caller, including the terminal completion.
Actual: the router has no registered turn queue when the notifications arrive, drops the terminal event during cleanup, and a subsequent wait for completion can block forever.
Root cause
The client registered the turn notification queue only after the RPC response arrived. The router buffered some unregistered notifications by turn ID, but treated an unregistered turn/completed as a cleanup-only event and discarded it.
PR-ready reference
I prepared a tested reference branch with a narrow reservation-and-replay fix:
- Commit: https://github.com/Hughhhhcoder/codex/commit/5b73a326836281eb4c2402fb08453d4fb6075a4c
- Branch:
codex/codex-message-router-completed
The client reserves the thread before sending turn/start, buffers early notifications, replays the response turn's notifications into its queue, and clears pending state if the request fails.
Validation
PYTHONPATH=src pytest -q tests/test_client_rpc_methods.py — 19 passed.
Because the current contribution policy does not accept external code contributions or pull requests, I am submitting this analysis and reference commit here instead of opening a PR. Please feel free to implement or adapt the fix.
1 Comment
Follow-up review found one ordering race in the reference implementation: the turn queue became routable before retained events finished replaying, so a notification arriving immediately after the turn/start response could overtake an earlier buffered event.
I moved pending replay under the router lock and added a regression test for the ordering guarantee.
Updated reference commit: https://github.com/Hughhhhcoder/codex/commit/9f567d004dc26d976a2ab470921ab2ade89087c7
Branch: codex/codex-message-router-completed
Validation: