bytebase/bytebase · error

failed to marshal tool call input: %s

Error message

failed to marshal tool call input: %s

What it means

chatClaude wraps errors.New when json.Marshal fails on a tool_use content block's Input field. Input is decoded as `any`, so the only realistic failures are unsupported types produced by a custom json.Unmarshaler or a decoding quirk (e.g. map keys of non-string kind via json.Number usage) — extremely rare with standard encoding/json output.

Source

Thrown at backend/api/v1/ai_service.go:472

	var resp chatClaudeResponse
	if err := json.Unmarshal(body, &resp); err != nil {
		return nil, errors.Errorf("failed to unmarshal Claude response: %s", err)
	}

	result := &v1pb.AIChatResponse{}
	if resp.Usage != nil {
		result.Usage = newAIChatUsage(resp.Usage.InputTokens + resp.Usage.OutputTokens)
	}
	var textContent string
	for _, block := range resp.Content {
		switch block.Type {
		case "text":
			textContent += block.Text
		case "tool_use":
			args, err := json.Marshal(block.Input)
			if err != nil {
				return nil, errors.Errorf("failed to marshal tool call input: %s", err)
			}
			result.ToolCalls = append(result.ToolCalls, &v1pb.AIChatToolCall{
				Id:        block.ID,
				Name:      block.Name,
				Arguments: string(args),
			})
		default:
		}
	}
	if textContent != "" {
		result.Content = &textContent
	}
	return result, nil
}

// Gemini chat types with tool-calling support.

type chatGeminiRequest struct {

View on GitHub (pinned to 1870550677)

Solutions

  1. Inspect block.Input's Go type at the failure point; ensure it only contains JSON-representable values.
  2. Avoid replacing map[string]any with non-JSON-marshalable key types during decoding.
  3. Include the tool name in the error message to identify which tool call failed, then reproduce against that tool's schema.
  4. If reproducible, capture and file the raw tool_use block against the provider SDK/API version in use.

Example fix

// before
args, err := json.Marshal(block.Input)
if err != nil {
	return nil, errors.Errorf("failed to marshal tool call input: %s", err)
}
// after: add tool context
args, err := json.Marshal(block.Input)
if err != nil {
	return nil, errors.Errorf("failed to marshal tool call input for tool %s (block %s): %s", block.Name, block.ID, err)
}
Defensive patterns

Strategy: type-guard

Validate before calling

func marshalable(v any) error {
	_, err := json.Marshal(v)
	return err
}

Type guard

if block.Input == nil {
	args = []byte("{}")
} else if err := marshalable(block.Input); err != nil { /* skip or report */ }

Try / catch

if err := json.Marshal(block.Input); err != nil {
	return nil, fmt.Errorf("tool %s input not serializable: %w", block.Name, err)
}

Prevention

When it happens

Trigger: Occurs while converting a Claude 'tool_use' content block to v1pb.AIChatToolCall when json.Marshal(block.Input) returns an error, typically only if block.Input contains a type encoding/json cannot serialize (unsupported func/channel, cyclic data) — practically only reachable with unusual custom decoding.

Common situations: Rarely seen in practice; could surface when the Claude API changes how tool inputs are represented or when a fork adds a custom unmarshaller producing non-marshalable values.

Understand the failure class

Background: json.Marshal / "failed to marshal" errors in Go: why "unsupported type" happens and how to fix it — this error's family across 22 libraries.

Related errors


AI-assisted analysis of bytebase/bytebase@1870550677 (2026-09-06). Data as JSON: /api/errors/9f1e61ae1b8caa2e. Report an issue: GitHub.