From c1191e78cb1d19239b445845d15e28b6c8d08c3d Mon Sep 17 00:00:00 2001 From: UGBOMEH OGOCHUKWU WILLIAMS Date: Sun, 19 Apr 2026 15:07:37 +0100 Subject: [PATCH] fix(run_agent): use safe attribute extraction instead of vars() on response objects MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit vars() raises TypeError on objects that don't expose __dict__ — this includes pydantic v2 models and certain OpenAI SDK response types. The crash occurred in the debug logging path when provider detection fell back to scanning response attributes. Replace vars(response) with a three-way fallback: check __dict__ first, then model_dump() for pydantic models, then an empty dict, wrapped in a try/except for any remaining edge cases. Fixes #6133 --- run_agent.py | 11 +++++- .../test_run_agent_codex_responses.py | 36 +++++++++++++++++++ 2 files changed, 46 insertions(+), 1 deletion(-) diff --git a/run_agent.py b/run_agent.py index 8e1fbfed194..fa8c22d8455 100644 --- a/run_agent.py +++ b/run_agent.py @@ -9499,7 +9499,16 @@ class AIAgent: # Check for x-openrouter-provider or similar metadata if provider_name == "Unknown" and response: # Log all response attributes for debugging - resp_attrs = {k: str(v)[:100] for k, v in vars(response).items() if not k.startswith('_')} + try: + if hasattr(response, "__dict__"): + _resp_dict = response.__dict__ + elif hasattr(response, "model_dump"): + _resp_dict = response.model_dump() + else: + _resp_dict = {} + resp_attrs = {k: str(v)[:100] for k, v in _resp_dict.items() if not k.startswith('_')} + except (TypeError, AttributeError): + resp_attrs = {} if self.verbose_logging: logging.debug(f"Response attributes for invalid response: {resp_attrs}") diff --git a/tests/run_agent/test_run_agent_codex_responses.py b/tests/run_agent/test_run_agent_codex_responses.py index 81213aaf673..4b4d13e8279 100644 --- a/tests/run_agent/test_run_agent_codex_responses.py +++ b/tests/run_agent/test_run_agent_codex_responses.py @@ -1310,3 +1310,39 @@ def test_preflight_codex_input_deduplicates_reasoning_ids(monkeypatch): # IDs must be stripped — with store=False the API 404s on id lookups. for it in reasoning_items: assert "id" not in it + + +# --------------------------------------------------------------------------- +# Issue #6133 — vars() on response objects that lack __dict__ raises TypeError. +# The fix must use a safe fallback (hasattr __dict__ / model_dump) instead. +# --------------------------------------------------------------------------- + + +def test_run_conversation_slots_response_does_not_raise(monkeypatch): + """Response objects that define __slots__ (and therefore have no __dict__) + must not crash the invalid-response debug logging path.""" + + class _SlotsResponse: + __slots__ = ("output", "output_text", "status") + + def __init__(self): + self.output = [] + self.output_text = None + self.status = "completed" + + agent = _build_agent(monkeypatch) + calls = {"n": 0} + + def _fake_api(api_kwargs): + calls["n"] += 1 + if calls["n"] == 1: + return _SlotsResponse() + return _codex_message_response("Recovered") + + monkeypatch.setattr(agent, "_interruptible_api_call", _fake_api) + + result = agent.run_conversation("ping") + + assert calls["n"] >= 2 + assert result["completed"] is True + assert result["final_response"] == "Recovered"