From 3664ab39145c813d8582d176c9544d766dba4469 Mon Sep 17 00:00:00 2001 From: Rahul Bansal Date: Sat, 21 Feb 2026 11:32:25 +0530 Subject: [PATCH] fix: sanitizeHistoryForProvider now repairs orphaned tool_use blocks Calls repairOrphanedToolPairs as the first step so that assistant messages with tool_calls that lack matching tool_results get synthetic results injected. This prevents Anthropic API 400 errors. Fixes #475 --- pkg/agent/context.go | 3 +++ pkg/agent/sanitize_test.go | 19 +++++++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/pkg/agent/context.go b/pkg/agent/context.go index a9db5afdd..661fc0a04 100644 --- a/pkg/agent/context.go +++ b/pkg/agent/context.go @@ -221,6 +221,9 @@ func sanitizeHistoryForProvider(history []providers.Message) []providers.Message return history } + // Repair orphaned tool_use/tool_result pairs before further sanitization. + history = repairOrphanedToolPairs(history) + sanitized := make([]providers.Message, 0, len(history)) for _, msg := range history { switch msg.Role { diff --git a/pkg/agent/sanitize_test.go b/pkg/agent/sanitize_test.go index 151c35936..6a848b7d9 100644 --- a/pkg/agent/sanitize_test.go +++ b/pkg/agent/sanitize_test.go @@ -78,6 +78,25 @@ func TestRepairOrphanedToolPairs_OrphanToolResultDropped(t *testing.T) { } } +func TestSanitizeHistoryForProvider_OrphanToolUseRepaired(t *testing.T) { + history := []providers.Message{ + {Role: "user", Content: "do something"}, + { + Role: "assistant", Content: "calling tool", + ToolCalls: []providers.ToolCall{{ID: "call_99", Name: "exec"}}, + }, + } + sanitized := sanitizeHistoryForProvider(history) + + if len(sanitized) != 3 { + t.Fatalf("expected 3 messages, got %d", len(sanitized)) + } + if sanitized[2].Role != "tool" || sanitized[2].ToolCallID != "call_99" { + t.Errorf("expected synthetic tool_result for call_99, got role=%q id=%q", + sanitized[2].Role, sanitized[2].ToolCallID) + } +} + func TestRepairOrphanedToolPairs_EmptyInput(t *testing.T) { repaired := repairOrphanedToolPairs(nil) if len(repaired) != 0 {