[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`
This commit is contained in:
guinness-oai
2026-06-17 19:13:38 -07:00
committed by GitHub
Unverified
parent f22d15b679
commit a306ac4ee3
2 changed files with 10 additions and 1 deletions
+1 -1
View File
@@ -1134,7 +1134,7 @@ impl Session {
pub(crate) async fn route_realtime_text_input(self: &Arc<Self>, 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,
@@ -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(_))
})