JuliusBrussee/caveman · error
contents
Error message
contents: %w
What it means
parseGeminiGenerateContent extracts the "contents" field from a Gemini GenerateContent request body and unmarshals it into req.Contents. If the value exists but does not match the expected geminiContent[] shape (wrong types, non-array, unexpected fields), the json.Unmarshal error is wrapped as "contents: %w" and TransformGemini fails.
Solutions
- Validate the request body against the Gemini GenerateContent schema; ensure "contents" is an array of {role, parts:[...]} objects.
- Inspect the wrapped %w error to find the exact JSON offset/type mismatch reported by encoding/json.
- Update the client SDK or fix the fixture so parts use supported fields (text, inline_data, functionResponse, etc.).
Example fix
// before
{"contents": "hello"}
// after
{"contents": [{"role": "user", "parts": [{"text": "hello"}]}]} Defensive patterns
Strategy: validation
Validate before calling
var probe struct {
Contents []json.RawMessage `json:"contents"`
}
if err := json.Unmarshal(body, &probe); err != nil {
return fmt.Errorf("contents must be an array of content objects")
} Type guard
func validContents(raw map[string]json.RawMessage) bool {
v, ok := raw["contents"]
if !ok {
return true
}
var arr []map[string]json.RawMessage
return json.Unmarshal(v, &arr) == nil
} Try / catch
req, err := parseGeminiGenerateContent(body)
if err != nil {
var ue *json.UnmarshalTypeError
if errors.As(err, &ue) {
return fmt.Errorf("contents field type mismatch at %s: %w", ue.Field, err)
}
return err
} Prevention
- Validate request bodies against the Gemini GenerateContent schema before transforming.
- Generate payloads with marshaling rather than string templates.
- Read the wrapped %w error — encoding/json reports the exact field and Go type that mismatched.
- Pin client SDK versions and re-check fixtures after upgrades.
When it happens
Trigger: TransformGemini receiving a request body whose "contents" is not an array of valid content parts — e.g. a string instead of an array, an object where parts have unexpected types, or null-typed fields where structured values are required.
Common situations: Proxying hand-crafted Gemini requests with malformed contents; a client SDK version emitting a schema the transform does not recognize; test fixtures with truncated or hand-edited contents arrays.
Understand the failure class
Background: "failed to unmarshal" / json.Unmarshal errors: why parsing a response into a Go struct fails and how to fix it — this error's family across 23 libraries.
Related errors
AI-assisted analysis of JuliusBrussee/caveman@3ee70a1026 (2026-09-20).
Data as JSON: /api/errors/305e8c4c523a1d94.
Report an issue: GitHub.
Appendix: source
Thrown at engine/pixel/transform_gemini.go:244
if opts.PriorWarmImageTokens > 0 {
def.PriorWarmImageTokens = opts.PriorWarmImageTokens
}
if opts.KeepSharp != nil {
def.KeepSharp = opts.KeepSharp
}
def.EmitRecoverable = opts.EmitRecoverable
return def
}
func parseGeminiGenerateContent(body []byte) (geminiGenerateContent, error) {
var raw map[string]json.RawMessage
if err := json.Unmarshal(body, &raw); err != nil {
return geminiGenerateContent{}, err
}
req := geminiGenerateContent{extra: raw}
if v, ok := raw["contents"]; ok {
if err := json.Unmarshal(v, &req.Contents); err != nil {
return geminiGenerateContent{}, fmt.Errorf("contents: %w", err)
}
}
if v, ok := raw["systemInstruction"]; ok && len(bytes.TrimSpace(v)) > 0 && !bytes.Equal(bytes.TrimSpace(v), []byte("null")) {
var inst geminiInstruction
if err := json.Unmarshal(v, &inst); err != nil {
return geminiGenerateContent{}, fmt.Errorf("systemInstruction: %w", err)
}
req.SystemInstruction = &inst
}
if v, ok := raw["tools"]; ok {
if err := json.Unmarshal(v, &req.Tools); err != nil {
return geminiGenerateContent{}, fmt.Errorf("tools: %w", err)
}
}
return req, nil
}
func geminiGlobalMediaResolution(raw map[string]json.RawMessage) (string, bool) {View on GitHub (pinned to 3ee70a1026)