From cfbb9fa9fea93f628dc708c347d395ead1e6719a Mon Sep 17 00:00:00 2001 From: Rahul Bansal Date: Sat, 21 Feb 2026 03:35:23 +0530 Subject: [PATCH] fix: prevent nil tool_use.input causing 400 Bad Request from Anthropic API The assistant message stored in loop.go omitted the Arguments map from ToolCall, leaving it nil. When sent back to Anthropic, nil serialized as JSON null instead of {}, which the API rejects. - Copy Arguments map into stored ToolCall in agent loop (root cause) - Add nil-guard in Anthropic provider to recover from Function.Arguments - Normalize ToolCalls when loading sessions from disk (migration) --- pkg/agent/loop.go | 7 ++++--- pkg/providers/anthropic/provider.go | 15 ++++++++++++++- pkg/session/manager.go | 8 ++++++++ 3 files changed, 26 insertions(+), 4 deletions(-) diff --git a/pkg/agent/loop.go b/pkg/agent/loop.go index b36f4a0c4..9a66c6cd1 100644 --- a/pkg/agent/loop.go +++ b/pkg/agent/loop.go @@ -635,9 +635,10 @@ func (al *AgentLoop) runLLMIteration( } assistantMsg.ToolCalls = append(assistantMsg.ToolCalls, providers.ToolCall{ - ID: tc.ID, - Type: "function", - Name: tc.Name, + ID: tc.ID, + Type: "function", + Name: tc.Name, + Arguments: tc.Arguments, Function: &providers.FunctionCall{ Name: tc.Name, Arguments: string(argumentsJSON), diff --git a/pkg/providers/anthropic/provider.go b/pkg/providers/anthropic/provider.go index b23236869..39e523ba4 100644 --- a/pkg/providers/anthropic/provider.go +++ b/pkg/providers/anthropic/provider.go @@ -146,7 +146,20 @@ func buildParams( blocks = append(blocks, anthropic.NewTextBlock(msg.Content)) } for _, tc := range msg.ToolCalls { - blocks = append(blocks, anthropic.NewToolUseBlock(tc.ID, tc.Arguments, tc.Name)) + input := tc.Arguments + if input == nil { + // Recover from nil Arguments (e.g. loaded from older sessions) + if tc.Function != nil && tc.Function.Arguments != "" { + var parsed map[string]interface{} + if err := json.Unmarshal([]byte(tc.Function.Arguments), &parsed); err == nil { + input = parsed + } + } + if input == nil { + input = map[string]interface{}{} + } + } + blocks = append(blocks, anthropic.NewToolUseBlock(tc.ID, input, tc.Name)) } anthropicMessages = append(anthropicMessages, anthropic.NewAssistantMessage(blocks...)) } else { diff --git a/pkg/session/manager.go b/pkg/session/manager.go index 08f0b0ad2..09e494bfd 100644 --- a/pkg/session/manager.go +++ b/pkg/session/manager.go @@ -259,6 +259,14 @@ func (sm *SessionManager) loadSessions() error { continue } + // Normalize tool calls to ensure Arguments map is populated + // from Function.Arguments for sessions saved before this fix. + for i, msg := range session.Messages { + for j, tc := range msg.ToolCalls { + session.Messages[i].ToolCalls[j] = providers.NormalizeToolCall(tc) + } + } + sm.sessions[session.Key] = &session }