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 <auto-fix@langchain.ai>
Co-authored-by: Palash Shah <palash@langchain.dev>
Co-authored-by: Palash Shah <35114859+Palashio@users.noreply.github.com>
This commit is contained in:
agent-forge-app[bot] 2026-03-10 12:56:44 -07:00 • committed by GitHub
parent 2f38484451
commit d98cd9ddc1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 92 additions and 0 deletions

View file

@ -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(

View file

@ -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"