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
- Inspect block.Input's Go type at the failure point; ensure it only contains JSON-representable values.
- Avoid replacing map[string]any with non-JSON-marshalable key types during decoding.
- Include the tool name in the error message to identify which tool call failed, then reproduce against that tool's schema.
- 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
- Keep tool input decoding strictly via encoding/json into any/map[string]any.
- Avoid custom unmarshallers that introduce non-JSON-marshalable types.
- Include tool name/id in error context for quick isolation.
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
- failed to marshal function call args: %s
- failed to unmarshal Claude response: %s
- failed to marshal Gemini request: %s
- failed to unmarshal Gemini response: %s
- invalid type
AI-assisted analysis of bytebase/bytebase@1870550677 (2026-09-06).
Data as JSON: /api/errors/9f1e61ae1b8caa2e.
Report an issue: GitHub.