JuliusBrussee/caveman · error

cacheengine: request root must be object

Error message

cacheengine: request root must be object

What it means

Thrown by inspectUniqueJSONValue when the ROOT JSON token is a scalar (string, number, bool, null) rather than an object or array delimiter. The engine only accepts JSON requests whose top level is an object, because provider request bodies are always objects and cache-marker lookup keys off object paths.

Source

Thrown at cacheengine/native.go:480

	if err != nil {
		return false, false
	}
	_, err = decoder.Token()
	return errors.Is(err, io.EOF), found
}

func inspectUniqueJSONValue(decoder *json.Decoder, root bool, depth int, provider string, path []string) (bool, error) {
	if depth > 512 {
		return false, errors.New("cacheengine: JSON nesting limit exceeded")
	}
	token, err := decoder.Token()
	if err != nil {
		return false, err
	}
	delim, composite := token.(json.Delim)
	if !composite {
		if root {
			return false, errors.New("cacheengine: request root must be object")
		}
		return false, nil
	}
	switch delim {
	case '{':
		seen := map[string]bool{}
		found := false
		for decoder.More() {
			keyToken, err := decoder.Token()
			if err != nil {
				return false, err
			}
			key, ok := keyToken.(string)
			if !ok || seen[key] {
				return false, errors.New("cacheengine: duplicate or invalid object key")
			}
			seen[key] = true
			matched := cacheMarkerAt(provider, path, key)

View on GitHub (pinned to 27d5a3981a)

Solutions

  1. Marshal the full request struct, not one of its fields
  2. If hand-building the body, ensure it starts with '{' — validate with a quick root-type check before sending
  3. Check test fixtures: a truncated .json file often starts with a scalar

Example fix

// before
body, _ := json.Marshal(req.Messages) // array, not the request object

// after
body, _ := json.Marshal(req) // {"model":..., "messages":[...]}
Defensive patterns

Strategy: validation

Validate before calling

func rootIsJSONObject(b []byte) bool {
    t := bytes.TrimLeft(b, " \t\r\n")
    return len(t) > 0 && t[0] == '{'
}

Type guard

// n/a

Prevention

When it happens

Trigger: Sending a body like "42", '"text"', 'true', or 'null' to the native engine; typically a client bug that marshals an inner value instead of the request struct, or writes a bare JSON fragment.

Common situations: json.Marshal on the wrong variable (a field instead of the struct); string interpolation where a pre-encoded fragment is sent directly; proxy or test harness posting a raw scalar fixture; note the wrapper may swallow this error and just report no cache marker found.

Related errors


AI-assisted analysis of JuliusBrussee/caveman@27d5a3981a (2026-08-15). Data as JSON: /api/errors/7d963d969d9e9269. Report an issue: GitHub.