Realtime handoff transcript can be duplicated by late same-turn transcript events
What issue are you seeing?
The Codex app-server realtime conversation path can persist the same user utterance twice when a handoff and late transcript events refer to the same underlying turn.
One observed ordering is:
input_transcript.addedsupplies provisional or completed user text.delegation.createdsupplies the authoritative handoffinput_transcript.- A late transcript event or
turn.donereplays the same utterance. - The active transcript or durable history contains a second final user entry.
For example, one spoken utterance can become:
user: continue with the investigation
user: continue with the investigation
This is not only a presentation problem. The duplicate exists at the app-server transcript/handoff boundary, before downstream UI rendering, and can therefore affect the transcript context handed to the next turn.
The shared failure is that transcript identity is lost while normalizing protocol events. Once item_id and turn_id are unavailable, the collector cannot reliably determine that the handoff transcript and a late transcript event describe the same utterance.
Text-based deduplication is not safe here. A user can intentionally say the same phrase twice, and those must remain two turns when their identities differ.
I found generic duplicate-output reports in the issue tracker, but none that cover this realtime handoff ordering and same-identity replay.
What steps can reproduce the bug?
This can be reproduced deterministically at the app-server protocol layer without microphone input:
- Start a realtime conversation using the V3/Frameless path with
clientManagedHandoffs: false. - Feed a user transcript event with an
item_idand/orturn_id. - Emit a
delegation.createdhandoff whoseinput_transcriptis the authoritative form of the same utterance. - After the handoff, emit a transcript-final or
turn.doneevent carrying the same item/turn identity. - Inspect the handoff
active_transcriptand subsequent transcript history.
The failing Frameless event order is:
input_transcript.added(item.id=A, text="continue with...")
delegation.created(item.id=D, input_transcript="continue with the investigation")
input_transcript.added(item.id=A, text="continue with the investigation")
turn.done(turn_id=T, ...)
Without identity-aware reconciliation, the final text can be appended after the authoritative handoff text rather than updating or suppressing the same logical entry.
A companion regression case is important:
input_transcript.added(item.id=A, text="okay")
turn.done(turn.id=T1, transcript="okay")
input_transcript.added(item.id=B, text="okay")
turn.done(turn.id=T2, transcript="okay")
These are distinct turns and must remain separate even though their text is identical.
The same reconciliation requirement applies to the shared V1/V2 event path, although the initially observed live failure was on V3/Frameless.
What is the expected behavior?
- One transcript identity produces at most one durable transcript entry.
- The handoff
input_transcriptis authoritative for the active utterance. - Late events with the same
item_idorturn_idupdate or idempotently reconcile that entry instead of appending another one. - Two different identities remain two entries, even if their text is exactly the same.
- Earlier completed utterances are not rewritten when a later handoff arrives.
- Transcript deltas remain useful for live display, while only authoritative lifecycle transitions affect durable history.
Additional information
Root-cause analysis
The realtime parsers receive useful wire identity, but the normalized RealtimeTranscriptDelta and RealtimeTranscriptDone events did not preserve enough of it. The transcript collector then had to infer relationships from event ordering, text, or list position.
That inference breaks when:
- a handoff arrives before a late final event;
- Frameless emits a post-handoff replay;
- a completed trailing input must be reconciled;
- V1 reports item identity later than the provisional text;
- two consecutive utterances intentionally contain identical text.
Focused fix validated locally
A local proof-of-concept carries optional item_id and turn_id through:
- the V1/V2/Frameless realtime parsers;
RealtimeTranscriptDeltaandRealtimeTranscriptDone;- app-server notifications and generated protocol schemas;
- a private transcript reconciliation state machine.
The state machine:
- matches entries by item/turn identity;
- treats handoff input as authoritative;
- suppresses only late replay of that same identity;
- preserves genuine repeated utterances with different identities;
- limits reconciliation to the active handoff region so earlier history is not rewritten.
Relevant local regression coverage includes:
frameless_handoff_reconciles_post_handoff_transcript_events_by_identityframeless_handoff_reconciles_completed_trailing_inputframeless_preserves_repeated_identical_separate_turnsv1_handoff_keeps_authoritative_input_when_late_done_has_item_identityv1_handoff_reconciles_active_input_with_later_assistant_contextframeless_handoff_does_not_replace_a_completed_earlier_inputframeless_output_identity_keeps_consecutive_items_separateinbound_handoff_request_reconciles_active_transcriptframeless_handoff_reconciles_late_transcript_and_repeated_utterance
Focused Codex API identity tests, Codex Core realtime integration tests, formatting, scoped Clippy, diff checks, and a locked release build passed.
The patched app-server was also exercised in a live voice session. Transcript deltas remained visible while speaking, then reconciled to one authoritative final entry. Repeated intentional utterances remained distinct.
The proof-of-concept is one atomic local commit based on 322d5b96cfa5c8fd52bd83ecfdb79cd9b330205f. Its scope is limited to the realtime parsers, shared protocol types, app-server notification schemas, transcript reconciler, and regression tests.
The branch is local and has not been pushed. I understand external contributions are invitation-only. I am happy to share or adapt the focused patch if a maintainer confirms the approach and invites a pull request.
Longer-term protocol recommendation
The focused fix works within the current architecture by preserving optional identity and reconciling it privately. A stronger long-term design would make transcript identity and lifecycle first-class at the app-server protocol boundary across V1, V2, and V3/Frameless.
Suggested contract:
- Every transcript delta, final, and handoff-bearing event carries a stable utterance/item identity plus its response or turn identity.
- The protocol represents explicit lifecycle transitions, for example:
provisionalordelta->authoritative_final-> optionalsuperseded/reconciled. - Display deltas are explicitly ephemeral; durable history accepts only authoritative lifecycle transitions.
- Consumers reconcile by stable identity, never by transcript text or history position.
- Repeating identical text under different identities creates separate entries.
- Replaying the same identity is idempotent.
- Schema invariants and cross-protocol fixtures cover late finals, handoffs, repeated utterances, output boundaries, and protocol-specific event order.
This would move the invariant into the protocol contract instead of requiring each consumer to infer transcript ownership from timing.
Environment
- Platform:
Darwin 25.5.0 arm64 - Codex Desktop:
26.721.41059 - Bundled Codex CLI/app-server family:
0.146.0-alpha.3.1 - Reproduced and validated with an app-server built from
openai/codexsource
No private transcript content, credentials, or session identifiers are needed to reproduce the issue.
1 Comment
@aibrahim-oai sorry to bother you over here too. But if we can get this and 35517 done, life is good with voice agents.