From fe483de4d3e7b5340e2dbb16253dca5914bbe31e Mon Sep 17 00:00:00 2001 From: honor2030 <19909783+honor2030@users.noreply.github.com> Date: Fri, 15 May 2026 15:13:11 +0900 Subject: [PATCH] fix(agent): keep max-iteration warnings out of quiet stdout Route the max-iterations diagnostic through logging when quiet_mode is active so automation wrappers keep stdout machine-readable. Add a regression test covering quiet max-iteration summary handling. --- agent/chat_completion_helpers.py | 17 +++++++++++++-- tests/run_agent/test_run_agent.py | 36 +++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+), 2 deletions(-) diff --git a/agent/chat_completion_helpers.py b/agent/chat_completion_helpers.py index 7af486dc5b..1917a2ede2 100644 --- a/agent/chat_completion_helpers.py +++ b/agent/chat_completion_helpers.py @@ -2848,8 +2848,21 @@ def try_activate_fallback(agent, reason: "FailoverReason | None" = None) -> bool def handle_max_iterations(agent, messages: list, api_call_count: int) -> str: """Request a summary when max iterations are reached. Returns the final response text.""" - agent._safe_print( - f"⚠️ Reached maximum iterations ({agent.max_iterations}). Requesting summary..." + warning = f"⚠️ Reached maximum iterations ({agent.max_iterations}). Requesting summary..." + if getattr(agent, "suppress_status_output", False): + # Strict machine-readable mode (hermes chat -Q, oneshot, background + # review): keep diagnostics out of stdout so wrappers receive only + # the final assistant content (#93220 class). Note: plain quiet_mode + # is NOT the right gate — the interactive CLI runs quiet_mode=True by + # default and should still see this warning. + logger.warning(warning) + else: + agent._safe_print(warning) + + summary_request = ( + "You've reached the maximum number of tool-calling iterations allowed. " + "Please provide a final response summarizing what you've found and accomplished so far, " + "without calling any more tools." ) summary_api_request_id = f"iteration-summary:{uuid.uuid4()}" diff --git a/tests/run_agent/test_run_agent.py b/tests/run_agent/test_run_agent.py index b497a5c1e7..6bbe15ed17 100644 --- a/tests/run_agent/test_run_agent.py +++ b/tests/run_agent/test_run_agent.py @@ -2689,6 +2689,42 @@ class TestHandleMaxIterations: outcome="success", ) + def test_suppress_status_output_keeps_iteration_warning_off_stdout(self, agent, capsys): + """Machine-readable mode (-Q/oneshot) must not contaminate stdout (#26155).""" + resp = _mock_response(content="Summary") + agent.client.chat.completions.create.return_value = resp + agent._cached_system_prompt = "You are helpful." + agent.suppress_status_output = True + + result = agent._handle_max_iterations( + [{"role": "user", "content": "do stuff"}], + 1, + ) + + captured = capsys.readouterr() + assert result == "Summary" + assert "Reached maximum iterations" not in captured.out + + def test_plain_quiet_mode_still_prints_iteration_warning(self, agent, capsys): + """Interactive CLI runs quiet_mode=True by default — the warning must + still show there; only suppress_status_output gates it (#26155).""" + resp = _mock_response(content="Summary") + agent.client.chat.completions.create.return_value = resp + agent._cached_system_prompt = "You are helpful." + agent.quiet_mode = True + agent.suppress_status_output = False + printed = [] + agent._print_fn = lambda *a, **k: printed.append(" ".join(str(x) for x in a)) + + result = agent._handle_max_iterations( + [{"role": "user", "content": "do stuff"}], + 1, + ) + + assert result == "Summary" + combined = "\n".join(printed) + capsys.readouterr().out + assert "Reached maximum iterations" in combined + def test_api_failure_returns_error(self, agent): agent.client.chat.completions.create.side_effect = Exception("API down") agent._cached_system_prompt = "You are helpful."