alibaba/open-code-review · error

invalid tool call arguments for %s: %w

Error message

invalid tool call arguments for %s: %w

What it means

Raised while converting OpenAI-style tool calls to the Anthropic message format. Tool-call function arguments arrive as a JSON string and must be unmarshaled into a map; if the string is non-empty but not valid JSON the conversion aborts with this error wrapping the json.Unmarshal failure. A valid-JSON `null` is accepted and treated as an empty input map (Anthropic rejects null input).

Source

Thrown at internal/llm/client.go:1288

		case "assistant":
			flushToolResults()
			// Reuse the native MessageParam whole to preserve thinking blocks
			// and signatures. Copy the slice to avoid mutating shared state
			// when the cache_control breakpoint writes below.
			if native, ok := msg.Native.Payload.(anthropic.MessageParam); ok && len(native.Content) > 0 {
				native.Content = append([]anthropic.ContentBlockParamUnion(nil), native.Content...)
				messages = append(messages, native)
				continue
			}
			var blocks []anthropic.ContentBlockParamUnion
			if s, ok := msg.Content.(string); ok && s != "" {
				blocks = append(blocks, anthropic.NewTextBlock(s))
			}
			for _, tc := range msg.ToolCalls {
				argsMap := map[string]any{}
				if tc.Function.Arguments != "" {
					if err := json.Unmarshal([]byte(tc.Function.Arguments), &argsMap); err != nil {
						return anthropic.MessageNewParams{}, fmt.Errorf("invalid tool call arguments for %s: %w", tc.Function.Name, err)
					}
					if argsMap == nil {
						// null arguments → empty map; Anthropic API rejects
						// null input (#382). Same guard as llmloop.parseToolArgs.
						argsMap = map[string]any{}
					}
				}
				blocks = append(blocks, anthropic.NewToolUseBlock(tc.ID, argsMap, tc.Function.Name))
			}
			if len(blocks) > 0 {
				messages = append(messages, anthropic.NewAssistantMessage(blocks...))
			} else {
				s, _ := msg.Content.(string)
				messages = append(messages, anthropic.NewAssistantMessage(anthropic.NewTextBlock(s)))
			}
		default:
			flushToolResults()
			switch content := msg.Content.(type) {

View on GitHub (pinned to 5cf97d0d15)

Solutions

  1. Inspect tc.Function.Arguments and fix the source producing invalid JSON
  2. Always generate arguments with json.Marshal(map[string]any{...}) rather than string concatenation
  3. Validate with json.Valid before passing the message through
  4. Use `null` or an empty object instead of a malformed string for no-argument tool calls

Example fix

// before
args := `{'path': 'x.go'}` // not valid JSON
// after
argsBytes, _ := json.Marshal(map[string]any{"path": "x.go"})
args := string(argsBytes)
Defensive patterns

Strategy: validation

Validate before calling

func validToolArgs(args string) bool {
	if args == "" || args == "null" { return true }
	var m map[string]any
	return json.Unmarshal([]byte(args), &m) == nil
}

Prevention

When it happens

Trigger: Building an anthropic.MessageNewParams from a message whose ToolCalls entry has Function.Arguments that is non-empty but not valid JSON — truncated JSON, single quotes, trailing commas, or a model emitting plain text as arguments.

Common situations: Malformed tool-call arguments produced by a model or upstream provider; arguments built by hand without json.Marshal; logs or fixtures pasted back as arguments; truncation at buffer boundaries.

Related errors


AI-assisted analysis of alibaba/open-code-review@5cf97d0d15 (2026-09-02). Data as JSON: /api/errors/859a73db2f99dca4. Report an issue: GitHub.