From 38b8d0da85aa663e4dd511eea6e084a4d6cf3697 Mon Sep 17 00:00:00 2001 From: annguyenNous Date: Sat, 23 May 2026 14:32:25 +0700 Subject: [PATCH] fix: emit guardrail halt message to client before closing stream MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When the tool loop guardrail fires (max_tool_failures, etc.), the turn exits with guardrail_halt but no final assistant message was emitted to the client. The SSE stream closed silently — indistinguishable from a crash. The stream_delta_callback(None) before tool execution is a display flush, not a hard close. After generating the halt response, emit it through both _safe_print (CLI) and stream_delta_callback (SSE) so clients see the explanation. Fixes #30770 --- agent/conversation_loop.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/agent/conversation_loop.py b/agent/conversation_loop.py index 212f7cc09ac..eed3cfa956b 100644 --- a/agent/conversation_loop.py +++ b/agent/conversation_loop.py @@ -3470,6 +3470,19 @@ def run_conversation( f"⚠️ Tool guardrail halted {decision.tool_name}: {decision.code}" ) messages.append({"role": "assistant", "content": final_response}) + # Emit the halt message to the client so it's not + # indistinguishable from a crash. The stream display + # was flushed (callback(None)) before tool execution, + # but the callback is still alive — fire the text + # through it so SSE/TUI clients see the explanation. + if final_response: + agent._safe_print(f"\n{final_response}\n") + if agent.stream_delta_callback: + try: + agent.stream_delta_callback(final_response) + agent.stream_delta_callback(None) + except Exception: + pass break # Reset per-turn retry counters after successful tool