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
- Marshal the full request struct, not one of its fields
- If hand-building the body, ensure it starts with '{' — validate with a quick root-type check before sending
- 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
- Always marshal the full request struct, never a field
- Add a root-type smoke check in tests for hand-built bodies
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
- cacheengine: duplicate or invalid object key
- cacheengine: invalid object close
- cacheengine: invalid array close
- cacheengine: invalid partition key
- cacheengine: negative traffic expectation
AI-assisted analysis of JuliusBrussee/caveman@27d5a3981a (2026-08-15).
Data as JSON: /api/errors/7d963d969d9e9269.
Report an issue: GitHub.