From ba4bc39afdbfc6eb0337bda334758fd8ca6a7cf9 Mon Sep 17 00:00:00 2001 From: kshitijk4poor <82637225+kshitijk4poor@users.noreply.github.com> Date: Thu, 20 Aug 2026 01:25:23 +0530 Subject: [PATCH] test(codex): pin the retention drop to real endpoints The salvaged compatibility test stubs `_is_codex_backend=lambda: False` on a SimpleNamespace, so it proves the helper honors its own boolean but not that the boolean is right for any real endpoint. A predicate change that widened the drop onto retention-supporting hosts would keep it green. Adds a parametrized test that builds a real AIAgent per base URL and asserts the drop only fires for chatgpt.com/backend-api/codex, while api.meta.ai, bedrock-mantle.*.api.aws, api.openai.com and a same-host/different-path backend keep their supported 24h value. Also asserts prompt_cache_key survives untouched on every endpoint, since retention and cache-key routing are independent and the guard must not disturb caching. Verified non-vacuous: relaxing the guard's condition to drop on every endpoint fails 4 of the 6 cases (Meta, Bedrock, OpenAI, non-codex path). Drive-by on the guard itself: drop the dead `None` default on the `pop` that is already gated by an `in` check, and record why the predicate is resolved via getattr -- run_codex_stream is driven with lightweight stand-in agents that lack `_is_codex_backend`, so a bare call would raise AttributeError. --- agent/codex_runtime.py | 6 +- .../test_run_agent_codex_responses.py | 57 +++++++++++++++++++ 2 files changed, 62 insertions(+), 1 deletion(-) diff --git a/agent/codex_runtime.py b/agent/codex_runtime.py index de273f7862..5f4bc2e939 100644 --- a/agent/codex_runtime.py +++ b/agent/codex_runtime.py @@ -1322,12 +1322,16 @@ def _sanitize_consumer_codex_request( rejected by the provider. """ sanitized = dict(request) + # Resolved defensively on purpose: run_codex_stream is also driven with + # lightweight stand-in agents that carry only the attributes a given path + # needs (see tests/agent/test_codex_request_transport_diagnostics.py), so a + # bare agent._is_codex_backend() here would raise AttributeError on them. backend_predicate = getattr(agent, "_is_codex_backend", None) is_consumer_codex = ( bool(backend_predicate()) if callable(backend_predicate) else False ) if is_consumer_codex and "prompt_cache_retention" in sanitized: - sanitized.pop("prompt_cache_retention", None) + sanitized.pop("prompt_cache_retention") logger.warning( "Dropped unsupported prompt_cache_retention at consumer Codex " "wire boundary (model=%s).", diff --git a/tests/run_agent/test_run_agent_codex_responses.py b/tests/run_agent/test_run_agent_codex_responses.py index a67091024b..bbe14ae79d 100644 --- a/tests/run_agent/test_run_agent_codex_responses.py +++ b/tests/run_agent/test_run_agent_codex_responses.py @@ -602,6 +602,63 @@ def test_consumer_codex_wire_guard_preserves_compatible_endpoint_retention(): assert sanitized is not request +@pytest.mark.parametrize( + "base_url,model,expect_dropped", + [ + # Consumer ChatGPT Codex: the endpoint rejects retention outright. + ("https://chatgpt.com/backend-api/codex", "gpt-5.6-sol", True), + ("https://chatgpt.com/backend-api/codex", "gpt-5-codex", True), + # Hosts that support 24h retention must keep it (#70083, #88601). + ("https://api.meta.ai/v1", "gpt-5.6-sol", False), + ("https://bedrock-mantle.us-east-1.api.aws/v1", "openai.gpt-5.5", False), + # Non-Codex OpenAI and a same-host/different-path backend are both + # outside the consumer-Codex contract the guard enforces. + ("https://api.openai.com/v1", "gpt-5.6-sol", False), + ("https://chatgpt.com/backend-api/other", "gpt-5.6-sol", False), + ], +) +def test_wire_guard_scopes_retention_drop_by_real_endpoint( + monkeypatch, + base_url, + model, + expect_dropped, +): + """The drop is scoped by the real endpoint, not by a stubbed predicate. + + The sibling test above pins the helper's contract against an explicit + boolean. This one drives ``_is_codex_backend()`` off a real ``AIAgent`` + built on each base URL, so a change to the hostname/path predicate that + widened the drop onto retention-supporting hosts would fail here. + """ + from agent.codex_runtime import _sanitize_consumer_codex_request + + _patch_agent_bootstrap(monkeypatch) + agent = run_agent.AIAgent( + model=model, + api_mode="codex_responses", + base_url=base_url, + api_key="codex-token", + quiet_mode=True, + max_iterations=4, + skip_context_files=True, + skip_memory=True, + ) + + request = { + "model": model, + "prompt_cache_key": "cache-key-sentinel", + "prompt_cache_retention": "24h", + "input": [{"role": "user", "content": "hi"}], + } + sanitized = _sanitize_consumer_codex_request(agent, request) + + assert ("prompt_cache_retention" not in sanitized) is expect_dropped + # Cache-key routing is independent of retention: the guard must never + # disturb the prompt-cache key, on any endpoint. + assert sanitized["prompt_cache_key"] == "cache-key-sentinel" + assert request["prompt_cache_retention"] == "24h" + + def test_run_codex_stream_returns_collected_items_when_stream_ends_without_terminal(monkeypatch): """The event-driven path tolerates streams that end without a terminal frame.