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

  1. 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
  2. For repeated message fields, do not use query/form binding - switch the endpoint to a JSON body (POST) with the json codec
  3. For maps, use the supported bracket syntax field[key]=value (one level) instead of dotted descent through the map
  4. Regenerate/verify the proto types in use so client and server agree on which fields are messages
  5. 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

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


AI-assisted analysis of go-kratos/kratos@668db92c2c (2026-08-16). Data as JSON: /api/errors/69431eaa746f732d. Report an issue: GitHub.