Python SDK can drop an early turn/completed notification

Open 💬 1 comment Opened Aug 27, 2026 by Hughhhhcoder

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:

  1. turn/started or a turn delta for the new turn.
  2. turn/completed for that same turn.
  3. Return the successful turn/start response.

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:

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.

View original on GitHub ↗

1 Comment

Hughhhhcoder · 20 hours ago

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:

  • PYTHONPATH=src python -m pytest -p no:cacheprovider -q tests/test_client_rpc_methods.py — 20 passed
  • Ruff 0.15.12 check and format verification passed for the changed files.