{"repo":"NousResearch/hermes-agent","public_id":"10175","display_label":"Graceful handling of invalid_encrypted_content in GPT-5 Responses sessions","summary":"The members appear to concern GPT-5 Responses/Codex sessions aborting on an invalid_encrypted_content error, with one issue describing the behavior and two pull requests proposing graceful recovery. Titles alone do not show the specifics of the fixes or their status.","subsystem":"agent","counts":{"members":3,"open_issues":0,"open_issues_distinct":0,"open_prs":0,"closed_issues":1,"merged_prs":1,"attempt_prs":1},"landed_fix":{"status":"merged_to_main","basis":"structural","pr":33035,"merged_at":"2026-05-27T05:01:18+00:00"},"resolution":{"state":"resolved","by":"pr-33035"},"freshness":{"state":"verified","last_verified_at":"2026-05-27T05:01:18+00:00","is_stale":false},"grouping":{"backbone":"STRUCTURAL_SPANNING","link_density":0.6666666666666666},"fix_edges":[{"issue_number":10175,"pr_number":33035,"tier":"verified","basis":"closing_ref","confidence":0.95,"recoverable_from_timeline":true,"pr_merged_at":"2026-05-27T05:01:18+00:00"}],"members":[{"kind":"issue","number":10175,"title":"GPT-5 Responses sessions abort on invalid_encrypted_content instead of degrading gracefully","url":null,"state":"closed"},{"kind":"pr","number":10144,"title":"fix: gracefully recover from invalid_encrypted_content","url":null,"state":"closed","close_reason":{"refs":[{"kind":"commit","value":"b6ca56f651505d6a8ec2489f1048da3d2c07d12e","number":0},{"kind":"pr","value":"#10144","number":10144},{"kind":"pr","value":"#33035","number":33035}],"category":"merged_via","implication":"superseded"},"merged":false},{"kind":"pr","number":33035,"title":"fix(codex-responses): gracefully recover from invalid_encrypted_content (salvage #10144)","url":null,"state":"merged","close_reason":{"category":"merged"},"merged":true,"is_fix_pr":true,"fix_tier":"verified","files_changed":["agent/agent_init.py","agent/chat_completion_helpers.py","agent/codex_responses_adapter.py","agent/conversation_loop.py","agent/error_classifier.py","agent/transports/codex.py","run_agent.py","scripts/release.py","tests/agent/test_error_classifier.py","tests/run_agent/test_run_agent_codex_responses.py"],"fix_hunks":[{"file":"agent/agent_init.py","hunks":["@@ -1005,6 +1005,13 @@ def init_agent(\n     \n     # Track conversation messages for session logging\n     agent._session_messages: List[Dict[str, Any]] = []\n+    # Responses encrypted reasoning replay state.  Some OpenAI-compatible\n+    # routes accept GPT-5 Responses requests but later reject replayed\n+    # encrypted reasoning blobs (HTTP 400 ``invalid_encrypted_content``).\n+    # When that happens we disable replay for the rest of the session and\n+    # fall back to stateless continuity.  See\n+    # agent/conversation_loop.py's invalid_encrypted_content retry branch.\n+    agent._codex_reasoning_replay_enabled = True\n     agent._memory_write_origin = \"assistant_tool\"\n     agent._memory_write_context = \"foreground\"\n     "]},{"file":"agent/chat_completion_helpers.py","hunks":["@@ -507,6 +507,9 @@ def build_api_kwargs(agent, api_messages: list) -> dict:\n             is_codex_backend=is_codex_backend,\n             is_xai_responses=is_xai_responses,\n             github_reasoning_extra=agent._github_models_reasoning_extra_body() if is_github_responses else None,\n+            replay_encrypted_reasoning=bool(\n+                getattr(agent, \"_codex_reasoning_replay_enabled\", True)\n+            ),\n         )\n \n     # ── chat_completions (default) ─────────────────────────────────────"]},{"file":"agent/codex_responses_adapter.py","hunks":["@@ -248,6 +248,7 @@ def _chat_messages_to_responses_input(\n     messages: List[Dict[str, Any]],\n     *,\n     is_xai_responses: bool = False,\n+    replay_encrypted_reasoning: bool = True,\n ) -> List[Dict[str, Any]]:\n     \"\"\"Convert internal chat-style messages to Responses input items.\n ","@@ -261,6 +262,14 @@ def _chat_messages_to_responses_input(\n     integration).  We now replay encrypted reasoning on every Responses\n     transport (xAI, native Codex, custom relays) and let xAI tell us\n     explicitly if a specific surface ever rejects a payload.\n+\n+    ``replay_encrypted_reasoning`` is the per-session kill switch.  Some\n+    OpenAI-compatible relays accept the request but later reject the\n+    replayed encrypted blob with HTTP 400 ``invalid_encrypted_content``;\n+    when that happens the retry loop calls\n+    ``AIAgent._disable_codex_reasoning_replay`` which both strips cached\n+    items from the conversation history and threads ``replay_enabled=False``\n+    through this converter so subsequent turns send no reasoning items.\n     \"\"\"\n     items: List[Dict[str, Any]] = []\n     seen_item_ids: set = set()","@@ -290,7 +299,11 @@ def _chat_messages_to_responses_input(\n                 # This applies to every Responses transport including\n                 # xAI — see _chat_messages_to_responses_input docstring\n                 # for the May 2026 reversal of the earlier xAI gate.\n-                codex_reasoning = msg.get(\"codex_reasoning_items\")\n+                codex_reasoning = (\n+                    msg.get(\"codex_reasoning_items\")\n+                    if replay_encrypted_reasoning\n+                    else None\n+                )\n                 has_codex_reasoning = False\n                 if isinstance(codex_reasoning, list):\n                     for ri in codex_reasoning:"]},{"file":"agent/conversation_loop.py","hunks":["@@ -1019,6 +1019,7 @@ def run_conversation(\n         nous_auth_retry_attempted=False\n         copilot_auth_retry_attempted=False\n         thinking_sig_retry_attempted = False\n+        invalid_encrypted_content_retry_attempted = False\n         image_shrink_retry_attempted = False\n         multimodal_tool_content_retry_attempted = False\n         oauth_1m_beta_retry_attempted = False","@@ -2296,6 +2297,49 @@ def _stop_spinner():\n                     )\n                     continue\n \n+                # ── Invalid encrypted reasoning replay recovery ───────\n+                # OpenAI Responses API surfaces (and some compatible relays)\n+                # return HTTP 400 ``invalid_encrypted_content`` when a\n+                # replayed ``codex_reasoning_items`` blob from a previous\n+                # turn fails verification (provider rotated the encryption\n+                # key, the route doesn't actually persist reasoning state,\n+                # etc.).  Recovery: disable replay for the rest of the\n+                # session, strip cached items from history, retry once.\n+                # One-shot — if a second 400 fires we fall through to the\n+                # normal retry/backoff path.  Only fires for codex_responses\n+                # mode with at least one assistant message that has cached\n+                # ``codex_reasoning_items``; without replay state, the\n+                # error is unrelated to our cache so the normal retry path\n+                # handles it (the provider is rejecting something else).\n+                if (\n+                    classified.reason == FailoverReason.invalid_encrypted_content\n+                    and not invalid_encrypted_content_retry_attempted\n+                    and agent.api_mode == \"codex_responses\"\n+                    and bool(getattr(agent, \"_codex_reasoning_replay_enabled\", True))\n+                    and any(\n+                        isinstance(_m, dict)\n+                        and _m.get(\"role\") == \"assistant\"\n+                        and isinstance(_m.get(\"codex_reasoning_items\"), list)\n+                        and _m.get(\"codex_reasoning_items\")\n+                        for _m in messages\n+                    )\n+                ):\n+                    invalid_encrypted_content_retry_attempted = True\n+                    replay_stats = agent._disable_codex_reasoning_replay(messages)\n+                    agent._vprint(\n+                        f\"{agent.log_prefix}⚠️  Encrypted reasoning replay was rejected by the provider — \"\n+                        f\"disabled replay and stripped {replay_stats['items']} item(s) from \"\n+                        f\"{replay_stats['messages']} message(s), retrying...\",\n+                        force=True,\n+                    )\n+                    logger.warning(\n+                        \"%sInvalid encrypted reasoning recovery: disabled replay and stripped %d items from %d messages\",\n+                        agent.log_prefix,\n+                        replay_stats[\"items\"],\n+                        replay_stats[\"messages\"],\n+                    )\n+                    continue\n+\n                 # ── llama.cpp grammar-parse recovery ──────────────────\n                 # llama.cpp's ``json-schema-to-grammar`` converter rejects\n                 # regex escape classes (``\\d``, ``\\w``, ``\\s``) and most"]},{"file":"agent/error_classifier.py","hunks":["@@ -50,6 +50,7 @@ class FailoverReason(enum.Enum):\n \n     # Request format\n     format_error = \"format_error\"        # 400 bad request — abort or strip + retry\n+    invalid_encrypted_content = \"invalid_encrypted_content\"  # Responses replay blob rejected — strip replay state and retry\n     multimodal_tool_content_unsupported = \"multimodal_tool_content_unsupported\"  # Provider rejected list-type content in tool messages (e.g. Xiaomi MiMo) — downgrade to text and retry\n \n     # Provider-specific","@@ -865,6 +866,26 @@ def _classify_400(\n             retryable=True,\n         )\n \n+    # Invalid encrypted reasoning replay blob (OpenAI Responses API).  Must be\n+    # checked BEFORE context_overflow because some surfaces emit messages that\n+    # contain context-like phrasing (\"encrypted content … could not be\n+    # verified\") which could otherwise trip the context_overflow heuristics.\n+    # ``error_msg`` is lowercased upstream — match accordingly.\n+    error_code_lower = (error_code or \"\").lower()\n+    if (\n+        error_code_lower == \"invalid_encrypted_content\"\n+        or \"invalid_encrypted_content\" in error_msg\n+        or (\n+            \"encrypted content for item\" in error_msg\n+            and \"could not be verified\" in error_msg\n+        )\n+    ):\n+        return result_fn(\n+            FailoverReason.invalid_encrypted_content,\n+            retryable=True,\n+            should_fallback=False,\n+        )\n+\n     # Context overflow from 400\n     if any(p in error_msg for p in _CONTEXT_OVERFLOW_PATTERNS):\n         return result_fn(","@@ -974,6 +995,13 @@ def _classify_by_error_code(\n             should_compress=True,\n         )\n \n+    if code_lower == \"invalid_encrypted_content\":\n+        return result_fn(\n+            FailoverReason.invalid_encrypted_content,\n+            retryable=True,\n+            should_fallback=False,\n+        )\n+\n     return None\n \n ","@@ -1141,15 +1169,49 @@ def _extract_error_code(body: dict) -> str:\n     \"\"\"Extract an error code string from the response body.\"\"\"\n     if not body:\n         return \"\"\n+\n+    def _code_from_payload(payload) -> str:\n+        \"\"\"Extract a code/type from a nested error payload dict (defensive).\"\"\"\n+        if not isinstance(payload, dict):\n+            return \"\"\n+        payload_error = payload.get(\"error\", {})\n+        if isinstance(payload_error, dict):\n+            nested = payload_error.get(\"code\") or payload_error.get(\"type\") or \"\"\n+            if isinstance(nested, str) and nested.strip() and nested.strip() != \"400\":\n+                return nested.strip()\n+        code = payload.get(\"code\") or payload.get(\"error_code\") or \"\"\n+        if isinstance(code, (str, int)):\n+            text = str(code).strip()\n+            if text and text != \"400\":\n+                return text\n+        return \"\"\n+\n     error_obj = body.get(\"error\", {})\n     if isinstance(error_obj, dict):\n         code = error_obj.get(\"code\") or error_obj.get(\"type\") or \"\"\n-        if isinstance(code, str) and code.strip():\n+        if isinstance(code, str) and code.strip() and code.strip() != \"400\":\n             return code.strip()\n+\n+        # Some providers wrap the real JSON error body as a string inside\n+        # error.message — peek into it for a nested code (e.g. Responses API\n+        # surfaces ``invalid_encrypted_content`` this way).\n+        message = error_obj.get(\"message\")\n+        if isinstance(message, str) and message.strip().startswith(\"{\"):\n+            import json\n+            try:\n+                inner = json.loads(message)\n+            except (json.JSONDecodeError, TypeError):\n+                inner = None\n+            nested_code = _code_from_payload(inner)\n+            if nested_code:\n+                return nested_code\n+\n     # Top-level code\n     code = body.get(\"code\") or body.get(\"error_code\") or \"\"\n     if isinstance(code, (str, int)):\n-        return str(code).strip()\n+        text = str(code).strip()\n+        if text and text != \"400\":\n+            return text\n     return \"\"\n \n "]},{"file":"agent/transports/codex.py","hunks":["@@ -27,6 +27,9 @@ def convert_messages(self, messages: List[Dict[str, Any]], **kwargs) -> Any:\n         return _chat_messages_to_responses_input(\n             messages,\n             is_xai_responses=bool(kwargs.get(\"is_xai_responses\")),\n+            replay_encrypted_reasoning=bool(\n+                kwargs.get(\"replay_encrypted_reasoning\", True)\n+            ),\n         )\n \n     def convert_tools(self, tools: List[Dict[str, Any]]) -> Any:","@@ -79,6 +82,9 @@ def build_kwargs(\n         is_github_responses = params.get(\"is_github_responses\", False)\n         is_codex_backend = params.get(\"is_codex_backend\", False)\n         is_xai_responses = params.get(\"is_xai_responses\", False)\n+        replay_encrypted_reasoning = bool(\n+            params.get(\"replay_encrypted_reasoning\", True)\n+        )\n \n         # Resolve reasoning effort\n         reasoning_effort = \"medium\"","@@ -100,6 +106,7 @@ def build_kwargs(\n             \"input\": _chat_messages_to_responses_input(\n                 payload_messages,\n                 is_xai_responses=is_xai_responses,\n+                replay_encrypted_reasoning=replay_encrypted_reasoning,\n             ),\n             \"tools\": response_tools,\n             \"store\": False,","@@ -121,7 +128,9 @@ def build_kwargs(\n             # replay them on subsequent turns for cross-turn coherence.\n             # See agent/codex_responses_adapter._chat_messages_to_responses_input\n             # for the May 2026 reversal of the earlier suppression gate.\n-            kwargs[\"include\"] = [\"reasoning.encrypted_content\"]\n+            kwargs[\"include\"] = (\n+                [\"reasoning.encrypted_content\"] if replay_encrypted_reasoning else []\n+            )\n             # xAI rejects `reasoning.effort` on grok-4 / grok-4-fast / grok-3\n             # / grok-code-fast / grok-4.20-0309-* with HTTP 400 even though\n             # those models reason natively. Only send the effort dial when","@@ -136,7 +145,9 @@ def build_kwargs(\n                     kwargs[\"reasoning\"] = github_reasoning\n             else:\n                 kwargs[\"reasoning\"] = {\"effort\": reasoning_effort, \"summary\": \"auto\"}\n-                kwargs[\"include\"] = [\"reasoning.encrypted_content\"]\n+                kwargs[\"include\"] = (\n+                    [\"reasoning.encrypted_content\"] if replay_encrypted_reasoning else []\n+                )\n         elif not is_github_responses and not is_xai_responses:\n             kwargs[\"include\"] = []\n "]},{"file":"run_agent.py","hunks":["@@ -717,6 +717,39 @@ def _emit_warning(self, message: str) -> None:\n             except Exception:\n                 logger.debug(\"status_callback error in _emit_warning\", exc_info=True)\n \n+    def _disable_codex_reasoning_replay(\n+        self,\n+        messages: Optional[List[Dict[str, Any]]] = None,\n+    ) -> Dict[str, int]:\n+        \"\"\"Disable Responses encrypted reasoning replay and strip cached state.\n+\n+        Called from the conversation_loop retry path when the provider\n+        rejects a replayed ``codex_reasoning_items`` blob with HTTP 400\n+        ``invalid_encrypted_content``.  Sets ``self._codex_reasoning_replay_enabled``\n+        to ``False`` (consumed by ``codex_responses_adapter._chat_messages_to_responses_input``\n+        and ``transports/codex.py`` to drop ``reasoning.encrypted_content``\n+        from subsequent requests) and pops ``codex_reasoning_items`` from\n+        every assistant message in ``messages`` so they cannot be replayed\n+        again later in the session.\n+\n+        Returns a small stats dict ``{\"messages\": int, \"items\": int}``\n+        counting what was stripped — purely for diagnostic logging.\n+        \"\"\"\n+        stripped_messages = 0\n+        stripped_items = 0\n+        target_messages = messages if isinstance(messages, list) else []\n+\n+        for msg in target_messages:\n+            if not isinstance(msg, dict) or msg.get(\"role\") != \"assistant\":\n+                continue\n+            items = msg.pop(\"codex_reasoning_items\", None)\n+            if isinstance(items, list) and items:\n+                stripped_messages += 1\n+                stripped_items += len(items)\n+\n+        self._codex_reasoning_replay_enabled = False\n+        return {\"messages\": stripped_messages, \"items\": stripped_items}\n+\n     # Stream-diagnostic class header preserved for backward compat —\n     # actual list lives in ``agent.stream_diag.STREAM_DIAG_HEADERS``.\n     from agent.stream_diag import STREAM_DIAG_HEADERS as _STREAM_DIAG_HEADERS  # noqa: E402"]},{"file":"scripts/release.py","hunks":["@@ -50,6 +50,7 @@\n     \"276689385+carltonawong@users.noreply.github.com\": \"carltonawong\",\n     \"[redacted:email]\": \"wangpuv\",\n     \"202622897+ticketclosed-wontfix@users.noreply.github.com\": \"ticketclosed-wontfix\",\n+    \"[redacted:email]\": \"victorGPT\",\n     # teknium (multiple emails)\n     \"[redacted:email]\": \"teknium1\",\n     \"[redacted:email]\": \"kenyonxu\","]},{"file":"tests/agent/test_error_classifier.py","hunks":["@@ -56,6 +56,7 @@ def test_enum_members_exist(self):\n             \"overloaded\", \"server_error\", \"timeout\",\n             \"context_overflow\", \"payload_too_large\", \"image_too_large\",\n             \"model_not_found\", \"format_error\",\n+            \"invalid_encrypted_content\",\n             \"multimodal_tool_content_unsupported\",\n             \"provider_policy_blocked\",\n             \"thinking_signature\", \"long_context_tier\",","@@ -144,6 +145,19 @@ def test_from_top_level_code(self):\n         body = {\"code\": \"model_not_found\"}\n         assert _extract_error_code(body) == \"model_not_found\"\n \n+    def test_from_wrapped_json_message(self):\n+        body = {\n+            \"error\": {\n+                \"message\": (\n+                    '{\"error\":{\"message\":\"The encrypted content for item rs_001 could not be verified. '\n+                    'Reason: Encrypted content could not be decrypted or parsed.\",'\n+                    '\"type\":\"invalid_request_error\",\"param\":\"\",\"code\":\"invalid_encrypted_content\"}}'\n+                ),\n+                \"type\": \"400\",\n+            }\n+        }\n+        assert _extract_error_code(body) == \"invalid_encrypted_content\"\n+\n     def test_empty_when_no_code(self):\n         assert _extract_error_code({}) == \"\"\n         assert _extract_error_code({\"error\": {\"message\": \"oops\"}}) == \"\"","@@ -535,6 +549,51 @@ def test_non_anthropic_400_with_signature_not_classified_as_thinking(self):\n         # Without \"thinking\" in the message, it shouldn't be thinking_signature\n         assert result.reason != FailoverReason.thinking_signature\n \n+    def test_invalid_encrypted_content_classified_as_retryable_replay_failure(self):\n+        body = {\n+            \"error\": {\n+                \"message\": (\n+                    '{\"error\":{\"message\":\"The encrypted content for item rs_001 could not be verified. '\n+                    'Reason: Encrypted content could not be decrypted or parsed.\",'\n+                    '\"type\":\"invalid_request_error\",\"param\":\"\",\"code\":\"invalid_encrypted_content\"}}'\n+                ),\n+                \"type\": \"400\",\n+            }\n+        }\n+        e = MockAPIError(\n+            \"Error code: 400 - invalid_encrypted_content\",\n+            status_code=400,\n+            body=body,\n+        )\n+        result = classify_api_error(e, provider=\"custom\", model=\"gpt-5.4\")\n+        assert result.reason == FailoverReason.invalid_encrypted_content\n+        assert result.retryable is True\n+        assert result.should_fallback is False\n+\n+    def test_invalid_encrypted_content_broad_message_match_does_not_catch_generic_parse_error(self):\n+        message = \"Encrypted content could not be decrypted or parsed.\"\n+        e = MockAPIError(\n+            message,\n+            status_code=400,\n+            body={\"error\": {\"message\": message}},\n+        )\n+        result = classify_api_error(e, provider=\"custom\", model=\"gpt-5.4\")\n+        assert result.reason == FailoverReason.format_error\n+        assert result.retryable is False\n+        assert result.should_fallback is True\n+\n+    @pytest.mark.parametrize(\"error_code\", [\"Invalid_Encrypted_Content\", \"INVALID_ENCRYPTED_CONTENT\"])\n+    def test_invalid_encrypted_content_code_is_case_insensitive_for_400(self, error_code):\n+        e = MockAPIError(\n+            \"Error code: 400 - bad request\",\n+            status_code=400,\n+            body={\"error\": {\"code\": error_code, \"message\": \"Bad request\"}},\n+        )\n+        result = classify_api_error(e, provider=\"custom\", model=\"gpt-5.4\")\n+        assert result.reason == FailoverReason.invalid_encrypted_content\n+        assert result.retryable is True\n+        assert result.should_fallback is False\n+\n     # ── Provider-specific: llama.cpp grammar-parse ──\n \n     def test_llama_cpp_grammar_parse_error(self):"]},{"file":"tests/run_agent/test_run_agent_codex_responses.py","hunks":["@@ -2041,3 +2041,107 @@ def test_preflight_codex_input_deduplicates_reasoning_ids(monkeypatch):\n     # IDs must be stripped — with store=False the API 404s on id lookups.\n     for it in reasoning_items:\n         assert \"id\" not in it\n+\n+\n+def test_run_conversation_codex_disables_reasoning_replay_after_invalid_encrypted_content(monkeypatch):\n+    agent = _build_agent(monkeypatch)\n+    agent.provider = \"custom\"\n+    agent.base_url = \"https://api.example.com/v1\"\n+\n+    request_payloads = []\n+\n+    class _InvalidEncryptedContentError(Exception):\n+        def __init__(self):\n+            super().__init__(\n+                \"Error code: 400 - The encrypted content for item rs_001 could not be verified. \"\n+                \"Reason: Encrypted content could not be decrypted or parsed.\"\n+            )\n+            self.status_code = 400\n+            self.body = {\n+                \"error\": {\n+                    \"message\": (\n+                        '{\"error\":{\"message\":\"The encrypted content for item rs_001 could not be verified. '\n+                        'Reason: Encrypted content could not be decrypted or parsed.\",'\n+                        '\"type\":\"invalid_request_error\",\"param\":\"\",\"code\":\"invalid_encrypted_content\"}}'\n+                    ),\n+                    \"type\": \"400\",\n+                }\n+            }\n+\n+    responses = [_InvalidEncryptedContentError(), _codex_message_response(\"Recovered without replay.\")]\n+\n+    def _fake_api_call(api_kwargs):\n+        request_payloads.append(api_kwargs)\n+        current = responses.pop(0)\n+        if isinstance(current, Exception):\n+            raise current\n+        return current\n+\n+    monkeypatch.setattr(agent, \"_interruptible_api_call\", _fake_api_call)\n+\n+    history = [\n+        {\n+            \"role\": \"assistant\",\n+            \"content\": \"\",\n+            \"finish_reason\": \"incomplete\",\n+            \"codex_reasoning_items\": [\n+                {\"type\": \"reasoning\", \"id\": \"rs_001\", \"encrypted_content\": \"enc_bad\", \"summary\": []},\n+            ],\n+        }\n+    ]\n+\n+    result = agent.run_conversation(\"continue\", conversation_history=history)\n+\n+    assert result[\"completed\"] is True\n+    assert result[\"final_response\"] == \"Recovered without replay.\"\n+    assert len(request_payloads) == 2\n+    assert any(item.get(\"type\") == \"reasoning\" for item in request_payloads[0][\"input\"])\n+    assert not any(item.get(\"type\") == \"reasoning\" for item in request_payloads[1][\"input\"])\n+    assert request_payloads[0].get(\"include\") == [\"reasoning.encrypted_content\"]\n+    assert request_payloads[1].get(\"include\") == []\n+    assert result[\"messages\"][0].get(\"codex_reasoning_items\") is None\n+    assert agent._codex_reasoning_replay_enabled is False\n+\n+\n+def test_run_conversation_codex_invalid_encrypted_content_without_replay_state_does_not_disable_replay(monkeypatch):\n+    agent = _build_agent(monkeypatch)\n+    agent.provider = \"custom\"\n+    agent.base_url = \"https://api.example.com/v1\"\n+    monkeypatch.setattr(run_agent, \"jittered_backoff\", lambda *args, **kwargs: 0)\n+\n+    request_payloads = []\n+\n+    class _InvalidEncryptedContentError(Exception):\n+        def __init__(self):\n+            super().__init__(\"Error code: 400 - bad request\")\n+            self.status_code = 400\n+            self.body = {\n+                \"error\": {\n+                    \"code\": \"INVALID_ENCRYPTED_CONTENT\",\n+                    \"message\": \"Bad request\",\n+                }\n+            }\n+\n+    responses = [_InvalidEncryptedContentError(), _codex_message_response(\"Recovered after generic retry.\")]\n+\n+    def _fake_api_call(api_kwargs):\n+        request_payloads.append(api_kwargs)\n+        current = responses.pop(0)\n+        if isinstance(current, Exception):\n+            raise current\n+        return current\n+\n+    monkeypatch.setattr(agent, \"_interruptible_api_call\", _fake_api_call)\n+\n+    result = agent.run_conversation(\n+        \"continue\",\n+        conversation_history=[{\"role\": \"assistant\", \"content\": \"No replay state here.\"}],\n+    )\n+\n+    assert result[\"completed\"] is True\n+    assert result[\"final_response\"] == \"Recovered after generic retry.\"\n+    assert len(request_payloads) == 2\n+    assert all(payload.get(\"include\") == [\"reasoning.encrypted_content\"] for payload in request_payloads)\n+    assert all(not any(item.get(\"type\") == \"reasoning\" for item in payload[\"input\"]) for payload in request_payloads)\n+    assert agent._codex_reasoning_replay_enabled is True\n+    assert result[\"messages\"][0].get(\"codex_reasoning_items\") is None\n"]}]}],"trust":{"default_tier":"verified","includes_suggested":false}}