Skip to content

fix(tools): use filtered messages list in async compaction#1124

Open
fede-kamel wants to merge 4 commits intoanthropics:mainfrom
fede-kamel:fix/async-compaction-bug-v2
Open

fix(tools): use filtered messages list in async compaction#1124
fede-kamel wants to merge 4 commits intoanthropics:mainfrom
fede-kamel:fix/async-compaction-bug-v2

Conversation

@fede-kamel
Copy link

Summary

Fixes a bug where async _check_and_compact() ignores the tool_use filtering logic.

The Bug

When compaction runs and the last message is an assistant with only tool_use blocks, those blocks should be filtered out before sending the summarization request. The sync version does this correctly, but the async version was using self._params["messages"] instead of the filtered messages variable.

Without fix:

BadRequestError: messages.2: `tool_use` ids were found without 
`tool_result` blocks immediately after: toolu_test123

With fix: Compaction succeeds - tool_use is filtered out.

Code Change

# Sync (line 202) - correct
messages = [*messages, ...]

# Async (line 453) - was buggy  
messages = [*self._params["messages"], ...]  # ignored filtering!

# Async (line 453) - fixed
messages = [*messages, ...]

Test Plan

  • Added regression test test_async_compaction_filters_tool_use
  • Test fails without fix, passes with fix
  • All existing tests pass
  • Verified with real API call

The async _check_and_compact() method was using self._params["messages"]
instead of the local `messages` variable when building the compaction
request. This caused the filtering logic (which removes tool_use blocks
from the last assistant message) to be ignored.

When compaction runs and the last message is an assistant with only
tool_use blocks, those blocks should be filtered out before sending
the summarization request. Without this fix, the API rejects with:

  "tool_use ids were found without tool_result blocks"

The sync version correctly uses `*messages`, the async version was
incorrectly using `*self._params["messages"]`.

Added regression test that verifies tool_use filtering works correctly.
@fede-kamel fede-kamel requested a review from a team as a code owner January 20, 2026 17:56
- Add cast() to fix pyright type error where lambda returns dict instead of ParseMessageCreateParamsBase
- Fix bug: use params["messages"] (existing conversation) instead of messages (input variable)
@fede-kamel
Copy link
Author

@RobertCraigie @karpetrosyan @dtmeadows — would appreciate a review on this when you have a moment.

This PR has been rebased on the latest main (no conflicts). Created a corresponding issue: #1205.

The bug: Async _check_and_compact() uses self._params["messages"] instead of the filtered local messages variable, causing tool_use blocks to be sent to the API without corresponding tool_result blocks. This results in a BadRequestError at runtime. The sync version handles this correctly.

Verified:

The fix is a one-line change. Happy to address any feedback. Thank you.

@fede-kamel
Copy link
Author

@RobertCraigie @karpetrosyan Friendly follow-up - this is a one-line bug fix for async message compaction. The bug causes BadRequestError at runtime when tool_use blocks are sent without corresponding tool_result. Test + reproduction gist included. Would appreciate a quick review. Thanks!



@pytest.mark.skipif(PYDANTIC_V1, reason="tool runner not supported with pydantic v1")
async def test_async_compaction_filters_tool_use(async_client: AsyncAnthropic) -> None:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM! Let’s move forward without this test. I’m working on some logic to make runner tests way simpler—no mocks, and much shorter

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right, the cast was unnecessary - pyright passes without it. Removed it. Thanks for catching that!

The actual bug fix remains. The test is removed as @karpetrosyan is working
on a new testing approach for runner tests that will be simpler.
@fede-kamel fede-kamel force-pushed the fix/async-compaction-bug-v2 branch from d3b1dc2 to 6b1d770 Compare February 27, 2026 13:32
@fede-kamel
Copy link
Author

@karpetrosyan Done! Removed the test as requested. The PR now contains only the one-line bug fix. Ready for approval when you get a chance. Thanks!

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@fede-kamel do we need to add a cast here? I think it should work with it

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right, the cast was unnecessary - pyright passes without it. Removed it. Thanks for catching that!

The cast was not needed - pyright passes without it.
Copy link
Collaborator

@karpetrosyan karpetrosyan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM! Thanks @fede-kamel

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants