fix(tools): preserve tool flow when caller appends unrelated messages (closes #1536)#1545
Open
0xdeadd wants to merge 1 commit into
Open
fix(tools): preserve tool flow when caller appends unrelated messages (closes #1536)#15450xdeadd wants to merge 1 commit into
0xdeadd wants to merge 1 commit into
Conversation
Closes anthropics#1536. The tool_runner's auto-append of (assistant, tool_result) was previously gated on a single `_messages_modified` flag that flipped to True whenever the caller called `append_messages` inside the loop body. This conflated two distinct use cases: 1. "Modifying tool results" — caller appends a custom tool_result to override the SDK's auto-generated one. Auto-append should skip. 2. "Append an unrelated user message" (anthropics#1536) — caller appends, say, "be concise in your response" between iterations. The tool_result still needs to be threaded back into history; otherwise the next request omits it, the model re-asks for the same tool call, and the loop spins forever. This change replaces the flag-based gate with a content-based check: auto-append is skipped only when the last user message in history already contains a tool_result block whose tool_use_id matches the one we were about to write. Unrelated messages don't match, so the tool flow stays correctly threaded. Sync and async `__run__` are updated symmetrically. The existing `test_custom_message_handling` xfail is preserved — it depends on additional re-threading logic and snapshot updates that are out of scope for this fix.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #1536.
The bug
The reporter found that calling
append_messagesinside thetool_runnerloop body causes an infinite tool-call loop. Their root-cause analysis is correct: anyappend_messagescall sets_messages_modified = True, which causes__run__to skip the auto-append of the assistant message + tool result. The next iteration sends a request with no tool result, the model re-asks for the same tool call, repeat forever.Why the flag is wrong
_messages_modifiedwas conflating two distinct use cases:tool_resultto override the SDK's auto-generated one. Auto-append should skip."be concise in your response"between iterations. The tool result still needs to be threaded back in, otherwise the loop breaks.A single boolean can't distinguish these — one means "I handled the tool flow," the other means "I added context, please continue normally."
The fix
Replace the flag-based gate with a content-based check: auto-append is skipped only when the last user message already contains a
tool_resultblock whosetool_use_idmatches the one we were about to write. Unrelated messages don't match, so the tool flow stays correctly threaded.Applied symmetrically to sync and async
__run__.Tests
test_tool_response_already_appended_detects_matching_tool_resultcovers:tool_result(modifying-tool-results case) → match → auto-append skippedtool_use_id→ no matchtests/lib/tools/test_runners.pypasses: 13 passed, 1 xfailed.test_custom_message_handlingxfail is preserved — that pattern depends on additional re-threading logic (inserting the assistant message before the caller's tool result) and snapshot updates that are out of scope for this fix.Repro from the issue
The reporter's repro (the "be concise" message inside the loop body) now terminates correctly: the tool result is auto-appended, the model sees its own tool answer, and produces the final text on the next iteration. Verified locally.