Skip to content

fix(memory): repair cross-session history restore producing orphaned tool_use blocks (#1383)#1466

Merged
bug-ops merged 3 commits intomainfrom
cross-session-history-restore
Mar 9, 2026
Merged

fix(memory): repair cross-session history restore producing orphaned tool_use blocks (#1383)#1466
bug-ops merged 3 commits intomainfrom
cross-session-history-restore

Conversation

@bug-ops
Copy link
Owner

@bug-ops bug-ops commented Mar 9, 2026

Summary

Fixes #1383 — cross-session history restore produced messages with tool_use blocks lacking corresponding tool_result blocks, causing Claude API 400 errors.

Five root causes identified and fixed:

  • RC3 (persistence.rs load_history): skip condition content.is_empty() dropped user messages that had empty text but non-empty parts (ToolResult). Fixed to content.is_empty() && parts.is_empty().
  • RC2 (persistence.rs strip_mid_history_orphans): only detected forward orphans (ToolUse without ToolResult). Added reverse pass that strips ToolResult parts whose tool_use_id has no matching ToolUse in the preceding assistant message.
  • RC1 (claude.rs split_messages_structured): downgraded unmatched ToolUse to text but left corresponding ToolResult as native AnthropicContentBlock, causing API 400. Now tracks emitted tool_use IDs and downgrades orphaned ToolResult blocks.
  • RC4 (claude.rs): system messages included in visible index array caused get(idx+1) to return a system message instead of the next user/assistant message. Filtered Role::System from visible.
  • RC5 (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 ToolResult
  • strip_orphans_keeps_complete_pair — complete pairs pass through intact
  • strip_orphans_mixed_history — orphans stripped, complete pairs kept
  • load_history_keeps_tool_only_user_message — empty-text user messages with ToolResult parts are not dropped
  • split_structured_downgrades_orphaned_tool_result — ToolResult downgraded when its ToolUse was downgraded
  • split_structured_system_not_in_visible — system messages excluded from visible index
  • persist_cancelled_tool_results_pairs_tool_use — cancellation path persists tombstone ToolResult
  • Reproduce original scenario: 3 memory_save calls → exit → new session → no API 400

…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.
@github-actions github-actions bot added bug Something isn't working size/XL Extra large PR (500+ lines) documentation Improvements or additions to documentation llm zeph-llm crate (Ollama, Claude) rust Rust code changes core zeph-core crate and removed bug Something isn't working labels Mar 9, 2026
@github-actions github-actions bot added the bug Something isn't working label Mar 9, 2026
@bug-ops bug-ops enabled auto-merge (squash) March 9, 2026 17:09
@bug-ops bug-ops merged commit 8a8e2dc into main Mar 9, 2026
18 checks passed
@bug-ops bug-ops deleted the cross-session-history-restore branch March 9, 2026 17:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working core zeph-core crate documentation Improvements or additions to documentation llm zeph-llm crate (Ollama, Claude) rust Rust code changes size/XL Extra large PR (500+ lines)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

bug: cross-session history restore regression — orphaned tool_use blocks cause API 400

1 participant