From cb49660bb445be35f9d01a0d7cd6fcb12f9952f5 Mon Sep 17 00:00:00 2001 From: kshitijk4poor <82637225+kshitijk4poor@users.noreply.github.com> Date: Mon, 14 Sep 2026 23:50:44 +0530 Subject: [PATCH] fix(codex): replay legacy endpoint-stamped reasoning without a model stamp Native compaction checkpoints and reasoning items persisted before model stamping existed carry only `_issuer_kind`. Treating a missing `_issuer_model` as foreign dropped every such item once the current model was known, which wiped existing sessions' native-compaction context on upgrade (four consumer tests in test_native_compaction / test_native_preflight_estimate / test_413_compression went red on the stack). Trust the endpoint stamp when no model stamp is present, as main does today. Items minted after this change carry the model stamp and still drop on a same-endpoint model switch; a wrong guess on a legacy item is caught by the invalid_encrypted_content 400 classifier and the replay kill switch. --- agent/codex_responses_adapter.py | 13 +++++++------ tests/agent/test_codex_responses_adapter.py | 15 ++++++++++----- 2 files changed, 17 insertions(+), 11 deletions(-) diff --git a/agent/codex_responses_adapter.py b/agent/codex_responses_adapter.py index ffac5e20fa..de6305a6fa 100644 --- a/agent/codex_responses_adapter.py +++ b/agent/codex_responses_adapter.py @@ -350,9 +350,8 @@ def _replay_reasoning_items( """Replay persisted encrypted reasoning/compaction items for one assistant turn. Skips duplicate ids, ``compaction`` checkpoints unless THIS request carries ``context_management`` (else a persisted checkpoint erases pre-checkpoint history on a model that cannot decrypt it), and items stamped by - another issuer or model (HTTP 400). Endpoint-stamped legacy items without model provenance drop - (fail closed) when the current model is known; fully unstamped items pass. ``id`` (store=False - lookups 404) and the Hermes provenance fields are stripped.""" + another issuer or model (HTTP 400). Items without a model stamp (legacy or unstamped) replay on a + matching issuer. ``id`` (store=False lookups 404) and the Hermes provenance fields are stripped.""" global _CROSS_ISSUER_WARN_EMITTED replayed: List[Dict[str, Any]] = [] for ri in _as_list(msg.get("codex_reasoning_items")): @@ -364,9 +363,11 @@ def _replay_reasoning_items( item_issuer = ri.get("_issuer_kind") item_model = ri.get("_issuer_model") foreign_issuer = current_issuer_kind is not None and item_issuer is not None and item_issuer != current_issuer_kind - foreign_model = current_issuer_model is not None and ( - (item_model is not None and item_model != current_issuer_model) - or (item_model is None and item_issuer is not None) + # No model stamp → trust the endpoint stamp. Native compaction checkpoints and reasoning persisted + # before model stamping exist carry none; dropping them would erase every existing session's + # context once. A wrong guess is caught by the invalid_encrypted_content 400 classifier. + foreign_model = ( + current_issuer_model is not None and item_model is not None and item_model != current_issuer_model ) if foreign_issuer or foreign_model: if not _CROSS_ISSUER_WARN_EMITTED: diff --git a/tests/agent/test_codex_responses_adapter.py b/tests/agent/test_codex_responses_adapter.py index 61894dc517..ec382875af 100644 --- a/tests/agent/test_codex_responses_adapter.py +++ b/tests/agent/test_codex_responses_adapter.py @@ -593,16 +593,21 @@ def test_reasoning_replay_requires_matching_issuer_model_on_same_endpoint(): assert not any(i.get("type") == "reasoning" for i in other) -def test_reasoning_replay_drops_endpoint_stamped_legacy_item_without_model(): - # Fail closed: an endpoint-only stamp cannot prove the minting model once the request model is known. +def test_legacy_endpoint_stamped_item_without_model_replays_on_same_issuer(): + # WHY: native compaction checkpoints and reasoning persisted before model stamping carry only the + # endpoint stamp; dropping them would erase every existing session's context once after upgrade. issuer = "other:https://responses.example.com/v1" legacy = {"type": "reasoning", "encrypted_content": "legacy-blob", "_issuer_kind": issuer} items = _chat_messages_to_responses_input( _reasoning_history(legacy), current_issuer_kind=issuer, current_issuer_model="gpt-5.6-sol" ) - assert not any(i.get("type") == "reasoning" for i in items) - # Ordinary assistant text stays replayable; only the sealed blob is withheld. - assert any(i.get("role") == "assistant" for i in items) + replayed = [i for i in items if i.get("type") == "reasoning"] + assert [i["encrypted_content"] for i in replayed] == ["legacy-blob"] + # A different endpoint stamp still drops. + foreign = _chat_messages_to_responses_input( + _reasoning_history(legacy), current_issuer_kind="codex_backend", current_issuer_model="gpt-5.6-sol" + ) + assert not any(i.get("type") == "reasoning" for i in foreign) def test_preflight_codex_api_kwargs_drops_oversized_message_id_end_to_end():