Conversation
…tool_use/tool_result blocks (#1383) Five root causes addressed: RC3: load_history() was dropping user messages with empty text but non-empty parts (ToolResult) before sanitization ran. Fix: only skip when both content and parts are empty. RC2: strip_mid_history_orphans() only detected forward orphans (ToolUse without ToolResult). Add reverse pass: strip ToolResult parts whose tool_use_id has no matching ToolUse in the preceding assistant message. RC1: split_messages_structured() downgraded unmatched ToolUse to text but left the corresponding ToolResult as native AnthropicContentBlock, causing API 400. Fix: track emitted tool_use IDs; downgrade ToolResult blocks referencing IDs not present in the preceding native ToolUse output. RC4: system messages were included in the visible index array inside split_messages_structured(), causing get(idx+1) to return a system message instead of the next user/assistant message. Filter Role::System from visible. RC5: cancellation after assistant message persist but before user message persist left orphaned ToolUse in the DB. Fix: persist tombstone ToolResult (is_error=true) on both early-return cancellation paths in handle_native_tool_calls.
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.
Summary
Fixes #1383 — cross-session history restore produced messages with
tool_useblocks lacking correspondingtool_resultblocks, causing Claude API 400 errors.Five root causes identified and fixed:
persistence.rsload_history): skip conditioncontent.is_empty()dropped user messages that had empty text but non-empty parts (ToolResult). Fixed tocontent.is_empty() && parts.is_empty().persistence.rsstrip_mid_history_orphans): only detected forward orphans (ToolUse without ToolResult). Added reverse pass that strips ToolResult parts whosetool_use_idhas no matching ToolUse in the preceding assistant message.claude.rssplit_messages_structured): downgraded unmatched ToolUse to text but left corresponding ToolResult as nativeAnthropicContentBlock, causing API 400. Now tracks emitted tool_use IDs and downgrades orphaned ToolResult blocks.claude.rs): system messages included invisibleindex array causedget(idx+1)to return a system message instead of the next user/assistant message. FilteredRole::Systemfromvisible.tool_execution.rs): cancellation after assistant message persist but before user persist left orphaned ToolUse in DB. Both early-return cancellation paths now persist tombstone ToolResult (is_error=true).Test plan
strip_orphans_removes_orphaned_tool_result— reverse pass strips unmatched ToolResultstrip_orphans_keeps_complete_pair— complete pairs pass through intactstrip_orphans_mixed_history— orphans stripped, complete pairs keptload_history_keeps_tool_only_user_message— empty-text user messages with ToolResult parts are not droppedsplit_structured_downgrades_orphaned_tool_result— ToolResult downgraded when its ToolUse was downgradedsplit_structured_system_not_in_visible— system messages excluded from visible indexpersist_cancelled_tool_results_pairs_tool_use— cancellation path persists tombstone ToolResultmemory_savecalls → exit → new session → no API 400