JuliusBrussee/caveman · error

text

Error message

text: %w

What it means

A gemini part type's UnmarshalJSON first decodes into a raw field map, stores it in Extra, then decodes the "text" key into a *string. If "text" is present but is not a JSON string (e.g. an object or number), the error is wrapped as "text: %w" and unmarshaling of the whole part fails.

Solutions

  1. Ensure each part's "text" is a plain JSON string: {"text": "message"}.
  2. Check the wrapped error for the JSON type that was found instead of a string.
  3. If the payload carries structured text, move the structure into a separate field or serialize it to a string first.

Example fix

// before
{"parts": [{"text": {"body": "hi"}}]}
// after
{"parts": [{"text": "hi"}]}
Defensive patterns

Strategy: validation

Validate before calling

var probe struct {
    Parts []struct {
        Text any `json:"text"`
    } `json:"parts"`
}
if err := json.Unmarshal(body, &probe); err != nil {
    return err
}
for i, p := range probe.Parts {
    if _, ok := p.Text.(string); p.Text != nil && !ok {
        return fmt.Errorf("part %d: text must be a JSON string", i)
    }
}

Type guard

func hasStringText(part map[string]json.RawMessage) bool {
    v, ok := part["text"]
    if !ok {
        return true
    }
    var s string
    return json.Unmarshal(v, &s) == nil
}

Try / catch

var req geminiGenerateContent
if err := json.Unmarshal(body, &req); err != nil {
    if strings.Contains(err.Error(), "text:") {
        return fmt.Errorf("a part's \"text\" is not a JSON string: %w", err)
    }
    return err
}

Prevention

When it happens

Trigger: Unmarshaling a Gemini request/response whose content part contains "text" with a non-string value — e.g. {"text": {"value": "hi"}} or {"text": 42} — anywhere TransformGemini or response handling decodes parts.

Common situations: Migrating payloads from providers where content blocks are objects; code that accidentally nests the string under an object; generated clients emitting typed text fields.

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/5ad54a1ba1278846. Report an issue: GitHub.

Appendix: source

Thrown at engine/pixel/transform_gemini.go:351

		b, err := json.Marshal(req.Tools)
		if err != nil {
			return nil, err
		}
		raw["tools"] = b
	}
	return json.Marshal(raw)
}

func (p *geminiPart) UnmarshalJSON(data []byte) error {
	var raw map[string]json.RawMessage
	if err := json.Unmarshal(data, &raw); err != nil {
		return err
	}
	p.Extra = raw
	if v, ok := raw["text"]; ok {
		var s string
		if err := json.Unmarshal(v, &s); err != nil {
			return fmt.Errorf("text: %w", err)
		}
		p.Text = &s
	}
	if v, ok := raw["inline_data"]; ok {
		var inline geminiInlineData
		if err := json.Unmarshal(v, &inline); err != nil {
			return fmt.Errorf("inline_data: %w", err)
		}
		p.InlineData = &inline
	}
	if v, ok := raw["functionResponse"]; ok {
		var fr geminiFunctionResponse
		if err := json.Unmarshal(v, &fr); err != nil {
			return fmt.Errorf("functionResponse: %w", err)
		}
		p.FunctionResponse = &fr
	}
	if v, ok := raw["functionCall"]; ok {

View on GitHub (pinned to 3ee70a1026)