From a306ac4ee31aaf72c6f9e7cb75663efdcc19a6f1 Mon Sep 17 00:00:00 2001 From: guinness-oai Date: Wed, 17 Jun 2026 19:13:38 -0700 Subject: [PATCH] [codex] Use unique IDs for realtime-routed turns (#28826) ## Why A durable realtime voice orchestrator can reconnect and resume through multiple fresh `Session` instances. Realtime handoffs were using the Session-local `auto-compact-N` counter as their turn identity, but that counter restarts at zero for every resumed Session. The durable thread could therefore accumulate duplicate turn IDs, violating the uniqueness assumptions made by app-server and web clients. In Codex Apps, a new delegated response stream could be attached to an older turn with the same ID, placing live output higher in history and putting turn-scoped actions at risk. Persisted rollout and reconstructed model-context order were already correct because raw response items remain append-only and chronological. This change restores unique identity for reconstructed and live turn surfaces. ## What changed - Generate a UUIDv7 specifically for each realtime-routed delegation. - Leave the existing `auto-compact-N` identity path unchanged for actual internal auto-compaction turns. - Extend the inbound realtime handoff integration test to require a UUID turn ID from `turn/started`. ## Verification - `just test -p codex-core inbound_handoff_request_starts_turn` - `just fix -p codex-core` - `just fmt` --- codex-rs/core/src/session/mod.rs | 2 +- codex-rs/core/tests/suite/realtime_conversation.rs | 9 +++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/codex-rs/core/src/session/mod.rs b/codex-rs/core/src/session/mod.rs index cf1912359..e21b5394d 100644 --- a/codex-rs/core/src/session/mod.rs +++ b/codex-rs/core/src/session/mod.rs @@ -1134,7 +1134,7 @@ impl Session { pub(crate) async fn route_realtime_text_input(self: &Arc, text: String) { handlers::user_input_or_turn_inner( self, - self.next_internal_sub_id(), + Uuid::now_v7().to_string(), Op::UserInput { items: vec![UserInput::Text { text, diff --git a/codex-rs/core/tests/suite/realtime_conversation.rs b/codex-rs/core/tests/suite/realtime_conversation.rs index 61fc38e8b..a2aa6d61d 100644 --- a/codex-rs/core/tests/suite/realtime_conversation.rs +++ b/codex-rs/core/tests/suite/realtime_conversation.rs @@ -50,6 +50,7 @@ use std::sync::Mutex; use std::time::Duration; use tokio::sync::oneshot; use tokio::time::timeout; +use uuid::Uuid; use wiremock::Match; use wiremock::Mock; use wiremock::Request as WiremockRequest; @@ -3019,6 +3020,14 @@ async fn inbound_handoff_request_starts_turn() -> Result<()> { }) .await; + let turn_id = loop { + let event = test.codex.next_event().await?; + if let EventMsg::TurnStarted(turn_started) = event.msg { + break turn_started.turn_id; + } + }; + Uuid::parse_str(&turn_id).context("realtime-routed turn ID should be a UUID")?; + wait_for_event(&test.codex, |event| { matches!(event, EventMsg::TurnComplete(_)) })