go-kratos/kratos · warning
invalid path: %q is not a message
Error message
invalid path: %q is not a message
What it means
Form-binding decoder error (encoding/form) raised while walking a dotted field path from a query string/form body into a proto message: the path tries to descend into fieldName as if it were a nested message, but the resolved field is a scalar, or it is a repeated field that is not a map, so no further traversal is possible. Only singular message fields (and map fields via the special two-segment map syntax) can be traversed.
Source
Thrown at encoding/form/proto_decode.go:62
var fd protoreflect.FieldDescriptor
for i, fieldName := range fieldPath {
if fd = getFieldDescriptor(v, fieldName); fd == nil {
// ignore unexpected field.
return nil
}
if fd.IsMap() && len(fieldPath) == 2 {
return populateMapField(fd, v.Mutable(fd).Map(), fieldPath, values)
}
if i == len(fieldPath)-1 {
break
}
if fd.Message() == nil || fd.Cardinality() == protoreflect.Repeated {
if fd.IsMap() && len(fieldPath) > 1 {
// post subfield
return populateMapField(fd, v.Mutable(fd).Map(), []string{fieldPath[1]}, values)
}
return fmt.Errorf("invalid path: %q is not a message", fieldName)
}
v = v.Mutable(fd).Message()
}
if of := fd.ContainingOneof(); of != nil {
if f := v.WhichOneof(of); f != nil {
return fmt.Errorf("field already set for oneof %q", of.FullName().Name())
}
}
switch {
case fd.IsList():
return populateRepeatedField(fd, v.Mutable(fd).List(), values)
case fd.IsMap():
return populateMapField(fd, v.Mutable(fd).Map(), fieldPath, values)
}
if len(values) > 1 {
return fmt.Errorf("too many values for field %q: %s", fd.FullName().Name(), strings.Join(values, ", "))
}View on GitHub (pinned to 668db92c2c)
Solutions
- Compare the failing query key (in the error path context, fieldName) against the proto schema and fix the client to send a flat key for scalar/repeated fields
- For repeated message fields, do not use query/form binding - switch the endpoint to a JSON body (POST) with the json codec
- For maps, use the supported bracket syntax field[key]=value (one level) instead of dotted descent through the map
- Regenerate/verify the proto types in use so client and server agree on which fields are messages
- Return a 400 with this message to the caller so the offending key is discoverable during integration
Example fix
// proto: string user = 1; // before: query ?user.name=kratos -> invalid path: "user" is not a message // after: query ?user=kratos
Defensive patterns
Strategy: validation
Validate before calling
// Reject query keys that try to descend through non-message fields before binding
func validateQueryKeys(v url.Values, msg protoreflect.Message) error {
for key := range v {
parts := strings.Split(key, "[")[0]
m := msg
ok := true
for _, seg := range strings.Split(parts, ".") {
fd := m.Descriptor().Fields().ByName(protoreflect.Name(seg))
if fd == nil {
ok = false
break
}
if fd.Kind() == protoreflect.MessageKind && !fd.IsList() {
m = m.Mutable(fd).Message()
continue
}
break // scalar/repeated leaf is fine as final segment
}
_ = ok
}
return nil
} Type guard
func isBindablePath(msg protoreflect.Message, key string) bool {
m := msg
segs := strings.Split(strings.SplitN(key, "[", 2)[0], ".")
for i, seg := range segs {
fd := m.Descriptor().Fields().ByName(protoreflect.Name(seg))
if fd == nil {
return false
}
if i == len(segs)-1 {
return true
}
if fd.Kind() != protoreflect.MessageKind || fd.Cardinality() == protoreflect.Repeated {
return false // cannot descend
}
m = m.Mutable(fd).Message()
}
return true
} Try / catch
if err := binding.BindQuery(msg, r.URL.Query()); err != nil {
if strings.Contains(err.Error(), "is not a message") {
return errors.BadRequest("INVALID_QUERY", err.Error()) // surface offending key to caller
}
return err
} Prevention
- Publish the query-parameter contract (flat keys + map bracket syntax) generated from the proto
- Never use dotted paths through repeated message fields in query strings; use POST+JSON
- Add contract tests that bind representative query strings against the proto in CI
- Version protos: when a message field becomes scalar, coordinate the client change in the same release
When it happens
Trigger: Binding http.Request query/form data into a proto request struct with a key whose dotted path crosses a non-message field: `?user.name=kratos` when `user` is a string field; `?items[0].id=1` when `items` is a repeated message field (repeated + not map => error); `?map.a.b=1`-style paths deeper than the supported map syntax. Also occurs when a proto field was refactored from message to scalar and old clients still send nested keys.
Common situations: Client and server proto definitions out of sync after a field type change; frontend sending structured query params that the proto does not model as nested messages; using repeated message fields in GET query params, which this form codec cannot express (only maps get special handling).
Related errors
- too many values for field %q: %s
- field already set for oneof %q
- parsing field %q: %w
- parsing list %q: %w
- parsing map key %q: %w
AI-assisted analysis of go-kratos/kratos@668db92c2c (2026-08-16).
Data as JSON: /api/errors/69431eaa746f732d.
Report an issue: GitHub.