From a8568749a182ff68ee131d992316fb9f457a4603 Mon Sep 17 00:00:00 2001 From: nesquena-hermes Date: Sat, 4 Jul 2026 23:06:21 +0000 Subject: [PATCH] fix(#5564): carry minted id onto eager-save checkpoint row in display merge MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Codex/Opus gate finding: in eager session-save mode the current user turn is checkpointed into s.messages before the agent runs (no id yet); after _assign_stable_message_ids stamps the returned result row, the display merge keeps the durable eager checkpoint and skips the stamped result — leaving the display row id-less while its context twin carries the minted id, silently defeating id-based fork/truncate alignment for eager-mode users. The merge now copies the minted id onto the kept checkpoint when it lacks one (guarded so it can't overwrite or duplicate). Opus verified cross-array id pairing holds across the real 3-turn transform pipeline. --- api/streaming.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/api/streaming.py b/api/streaming.py index 6d1c920b..27b1a9ff 100644 --- a/api/streaming.py +++ b/api/streaming.py @@ -5264,6 +5264,18 @@ def _merge_display_messages_after_agent_result(previous_display, previous_contex # before the agent runs. When the agent returns that same user turn # in result_messages, keep the durable checkpoint and append only # the assistant/tool delta. + # The eager checkpoint was written before _assign_stable_message_ids + # stamped the result rows, so it has no `id` while the context copy + # does — which would silently defeat id-based fork/truncate alignment + # for eager-mode users. Carry the minted id onto the kept checkpoint + # so display and context share it (#5564). + if ( + isinstance(msg, dict) + and msg.get('id') is not None + and isinstance(merged[-1], dict) + and merged[-1].get('id') is None + ): + merged[-1]['id'] = msg['id'] continue if ( key is not None