fix(ai): dedupe duplicate tool_results after compaction/batch (non-retryable 400)#1276
Merged
Merged
Conversation
…tryable 400) Providers (Anthropic via OpenRouter) fold consecutive `tool` messages into one user turn and reject >1 tool_result per tool_use id: "each tool_use must have a single result. Found multiple tool_result blocks with id X" — a NON-retryable 400 that aborted the run. Root cause: _dispatch_and_collect grouped batch results with itertools.groupby, which groups only *consecutive* keys. Batch results (_run_batch) interleave by tool_call_id, so an interleaved id yielded several groups → several tool_result messages for one tool_use. History trim/compaction can restructure the window the same way. Fixes: - Group by an order-preserving dict (not groupby) so each tool_call_id yields exactly one result regardless of arrival order. - Add _dedupe_tool_results to the pre-LLM repair (_repair_orphan_tool_uses): within each run of consecutive tool messages keep the first result per id, drop the rest. This runs proactively before every call AND on the 400-repair-retry path — so the "multiple tool_result blocks" 400 is now repaired and retried instead of failing fast as non-retryable. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NNjPggRSVZ2xnLb7ZxWP5H
Contributor
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
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.
Bug (hit during CLI testing, after compaction)
Providers fold consecutive
toolmessages into a single user turn and reject more than onetool_resultpertool_useid — a non-retryable 400 that aborts the whole run.Root cause
_dispatch_and_collectgrouped batch results withitertools.groupby, which groups only consecutive equal keys. In batch mode (_run_batch) results interleave bytool_call_id(e.g.[X, Y, X]), sogroupbyproduced several groups for one id →add_tool_resultfired more than once for it → duplicate consecutivetoolmessages → litellm folds them into one user turn with duplicatetool_resultblocks → 400. History trim/compaction restructures the window the same way (hence the correlation with compaction).Fix (source + safety net)
collectedby an ordereddictinstead ofgroupby, so eachtool_call_idyields exactly one result regardless of arrival order (also fixes token accounting for interleaved batches)._dedupe_tool_resultsin_repair_orphan_tool_uses— within each run of consecutivetoolmessages, keep the first result per id, drop the rest. Runs proactively before every LLM call and on the 400-repair-retry path, so the "multiple tool_result blocks" 400 is now repaired + retried instead of failing fast.Tests
TestDedupeToolResults(drop consecutive dup, no-op when unique, per-run scoping, repair integration) +call_llmduplicate-400 → repaired-and-retried. Full AI suite: no new failures vs branch baseline.Targets
ai-resiliency(#1241).🤖 Generated with Claude Code