From 3eb937a4985df1ca2efa64abf7c5949f1c6068a0 Mon Sep 17 00:00:00 2001 From: HexLab98 Date: Fri, 10 Jul 2026 07:10:18 +0700 Subject: [PATCH] fix(cron): preserve composed reports when verify-on-stop exhausts budget Clear stale final_response before verify-on-stop/pre_verify loop continues, and normalize iteration-limit exit reasons in turn_finalizer when a composed answer survives with unknown/budget_exhausted. Fixes #61631 --- agent/conversation_loop.py | 6 ++++++ agent/turn_finalizer.py | 18 ++++++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/agent/conversation_loop.py b/agent/conversation_loop.py index 4295bdad602..4f29e8249ba 100644 --- a/agent/conversation_loop.py +++ b/agent/conversation_loop.py @@ -5173,6 +5173,11 @@ def run_conversation( # terminal. Keep a debug breadcrumb in agent.log for tracing. logger.debug("verification stop-loop nudge issued (attempt %d)", agent._verification_stop_nudges) + # The attempted answer lives in ``final_msg`` / ``messages`` for + # the verify loop; do not keep a stale ``final_response`` that + # would skip turn_finalizer's iteration-limit normalization. + # (#61631) + final_response = None continue # User verification-loop gate: when the agent edited code this @@ -5224,6 +5229,7 @@ def run_conversation( agent._session_messages = messages logger.debug("pre_verify nudge issued (attempt %d)", agent._pre_verify_nudges) + final_response = None continue messages.append(final_msg) diff --git a/agent/turn_finalizer.py b/agent/turn_finalizer.py index 5eaad31848c..108262e16c8 100644 --- a/agent/turn_finalizer.py +++ b/agent/turn_finalizer.py @@ -121,6 +121,24 @@ def finalize_turn( exc_info=True, ) + elif ( + final_response is not None + and ( + api_call_count >= agent.max_iterations + or agent.iteration_budget.remaining <= 0 + ) + and not str(_turn_exit_reason).startswith("text_response(") + and not str(_turn_exit_reason).startswith("max_iterations_reached(") + ): + # verify-on-stop can assign ``final_response`` then ``continue`` the + # loop until the iteration budget is gone, leaving a composed answer + # with a non-success exit reason (``unknown``, ``budget_exhausted``). + # Normalize so cron's graceful-delivery exemption can match. + # (#61631) + _turn_exit_reason = ( + f"max_iterations_reached({api_call_count}/{agent.max_iterations})" + ) + # Determine if conversation completed successfully normal_text_response = str(_turn_exit_reason).startswith("text_response(") completed = (