diff --git a/agent/transports/codex.py b/agent/transports/codex.py index 4ee2277dc7..556aa6c133 100644 --- a/agent/transports/codex.py +++ b/agent/transports/codex.py @@ -220,6 +220,10 @@ def _alias_wire_tools(response_tools: Any, params: dict[str, Any], is_xai_respon return response_tools, wire_aliases +# Models already warned that an explicit disable has no wire form on their route (one warning per process). +_UNPROJECTABLE_DISABLE_WARNED: set[str] = set() + + def _resolve_reasoning(model: str, params: dict[str, Any]) -> tuple[Any, bool]: """``(effort, enabled)`` for the request, effort clamped (never escalated) to the endpoint's vocabulary. @@ -261,6 +265,14 @@ def _resolve_reasoning(model: str, params: dict[str, Any]) -> tuple[Any, bool]: return None, False if not reasoning_enabled: has_none = any(str(level).strip().lower() == "none" for level in supported) + if not has_none and model not in _UNPROJECTABLE_DISABLE_WARNED: + # #75227: report the unsupported configuration instead of silently falling back. + _UNPROJECTABLE_DISABLE_WARNED.add(model) + logger.warning( + "reasoning_effort: none cannot be sent for %s — its route accepts only %s, so the model's " + "default effort stays on (an omitted reasoning field does not disable it).", + model, ", ".join(str(level) for level in supported), + ) return ("none" if has_none else None), False return clamp_effort(reasoning_effort, supported), reasoning_enabled diff --git a/tests/agent/transports/test_codex_transport.py b/tests/agent/transports/test_codex_transport.py index 58c4a2be94..bfc3bc50d0 100644 --- a/tests/agent/transports/test_codex_transport.py +++ b/tests/agent/transports/test_codex_transport.py @@ -1886,6 +1886,18 @@ class TestOpenAIReasoningWireProjection: # Astra's vocabulary has no ``none``: nothing to send, never an escalated level. assert self._reasoning(transport, "gpt-6-astra", {"enabled": False}) is None + def test_disable_the_route_cannot_express_is_reported_once(self, transport, caplog): + """#75227: a disable the vocabulary cannot carry (Astra has no ``none``) is reported as an unsupported + configuration — the model's default effort stays on — instead of silently omitted; once per model.""" + import logging + from agent.transports import codex as codex_transport + codex_transport._UNPROJECTABLE_DISABLE_WARNED.discard("gpt-6-astra") + with caplog.at_level(logging.WARNING, logger="agent.transports.codex"): + for _ in range(2): + assert self._reasoning(transport, "gpt-6-astra", {"enabled": False}) is None + warned = [r.getMessage() for r in caplog.records if "reasoning_effort: none" in r.getMessage()] + assert len(warned) == 1 and "gpt-6-astra" in warned[0], caplog.text + @pytest.mark.parametrize("model", ["gpt-4o-mini", "gpt-4.1-mini", "openai/gpt-4o", "ft:gpt-4o-mini:acme::abc1"]) def test_chat_era_openai_models_get_no_reasoning_field_on_the_official_origin(self, transport, model): for rc in (None, {"enabled": True, "effort": "high"}, {"enabled": False}):