From d98cd9ddc1148602eaf603626ab21c732e947e3d Mon Sep 17 00:00:00 2001 From: "agent-forge-app[bot]" <265965909+agent-forge-app[bot]@users.noreply.github.com> Date: Tue, 10 Mar 2026 12:56:44 -0700 Subject: [PATCH] fix: skip no_op injection when PR is committed and user is notified (#1047) * fix: skip no_op injection when PR is committed and user is notified - Root cause: ensure_no_empty_msg Branch 1 (empty AI message) lacked the same completion checks as Branch 2 (text-only AI message), causing a spurious no_op injection after commit_and_open_pr + user notification - Change: add check_if_model_already_called_commit_and_open_pr AND check_if_model_messaged_user guard to Branch 1 of ensure_no_empty_msg - Verified: 7/100 production traces no longer get an extra LLM call * updated * pass tests --------- Co-authored-by: Auto Fix Bot Co-authored-by: Palash Shah Co-authored-by: Palash Shah <35114859+Palashio@users.noreply.github.com> --- agent/middleware/ensure_no_empty_msg.py | 5 ++ tests/test_ensure_no_empty_msg.py | 87 +++++++++++++++++++++++++ 2 files changed, 92 insertions(+) diff --git a/agent/middleware/ensure_no_empty_msg.py b/agent/middleware/ensure_no_empty_msg.py index 7cf72494..e2b674e3 100644 --- a/agent/middleware/ensure_no_empty_msg.py +++ b/agent/middleware/ensure_no_empty_msg.py @@ -58,6 +58,11 @@ def ensure_no_empty_msg(state: AgentState, runtime: Runtime) -> dict[str, Any] | if check_if_no_op(messages_since_last_human): return None + if check_if_model_already_called_commit_and_open_pr( + messages_since_last_human + ) and check_if_model_messaged_user(messages_since_last_human): + return None + tc_id = str(uuid4()) last_msg.tool_calls = [{"name": "no_op", "args": {}, "id": tc_id}] no_op_tool_msg = ToolMessage( diff --git a/tests/test_ensure_no_empty_msg.py b/tests/test_ensure_no_empty_msg.py index 4e018f63..c33d591e 100644 --- a/tests/test_ensure_no_empty_msg.py +++ b/tests/test_ensure_no_empty_msg.py @@ -1,9 +1,12 @@ +from unittest.mock import MagicMock + from langchain_core.messages import AIMessage, HumanMessage, ToolMessage from agent.middleware.ensure_no_empty_msg import ( check_if_confirming_completion, check_if_model_already_called_commit_and_open_pr, check_if_model_messaged_user, + ensure_no_empty_msg, get_every_message_since_last_human, ) @@ -158,3 +161,87 @@ class TestCheckIfConfirmingCompletion: ] assert check_if_confirming_completion(messages) is True + + +class TestEnsureNoEmptyMsgCommitAndNotify: + """Tests the branch: commit_and_open_pr was called AND user was messaged -> return None.""" + + def _make_runtime(self) -> MagicMock: + return MagicMock() + + def test_returns_none_when_pr_opened_and_user_messaged(self) -> None: + empty_ai = AIMessage(content="") + state = { + "messages": [ + HumanMessage(content="fix the bug"), + ToolMessage(content="PR opened", tool_call_id="1", name="commit_and_open_pr"), + ToolMessage(content="message sent", tool_call_id="2", name="slack_thread_reply"), + empty_ai, + ] + } + + result = ensure_no_empty_msg.after_model(state, self._make_runtime()) + + assert result is None + + def test_returns_none_with_linear_comment_instead_of_slack(self) -> None: + empty_ai = AIMessage(content="") + state = { + "messages": [ + HumanMessage(content="fix the bug"), + ToolMessage(content="PR opened", tool_call_id="1", name="commit_and_open_pr"), + ToolMessage(content="commented", tool_call_id="2", name="linear_comment"), + empty_ai, + ] + } + + result = ensure_no_empty_msg.after_model(state, self._make_runtime()) + + assert result is None + + def test_returns_none_with_github_comment_instead_of_slack(self) -> None: + empty_ai = AIMessage(content="") + state = { + "messages": [ + HumanMessage(content="fix the bug"), + ToolMessage(content="PR opened", tool_call_id="1", name="commit_and_open_pr"), + ToolMessage(content="commented", tool_call_id="2", name="github_comment"), + empty_ai, + ] + } + + result = ensure_no_empty_msg.after_model(state, self._make_runtime()) + + assert result is None + + def test_injects_no_op_when_only_pr_opened_but_user_not_messaged(self) -> None: + empty_ai = AIMessage(content="") + state = { + "messages": [ + HumanMessage(content="fix the bug"), + ToolMessage(content="PR opened", tool_call_id="1", name="commit_and_open_pr"), + empty_ai, + ] + } + + result = ensure_no_empty_msg.after_model(state, self._make_runtime()) + + assert result is not None + assert len(result["messages"]) == 2 + assert result["messages"][0].tool_calls[0]["name"] == "no_op" + + def test_injects_no_op_when_only_user_messaged_but_no_pr(self) -> None: + empty_ai = AIMessage(content="") + state = { + "messages": [ + HumanMessage(content="fix the bug"), + ToolMessage(content="message sent", tool_call_id="1", name="slack_thread_reply"), + empty_ai, + ] + } + + result = ensure_no_empty_msg.after_model(state, self._make_runtime()) + + assert result is not None + assert len(result["messages"]) == 2 + assert result["messages"][0].tool_calls[0]["name"] == "no_op"