go-kratos/kratos · error

not proto message

Error message

not proto message

What it means

'not proto message' (encoding/proto/proto.go:46) is returned by getProtoMessage during codec.Unmarshal for the 'proto' codec. The helper accepts a proto.Message directly or unwraps any chain of pointers (val.Elem().Interface() recursion), but as soon as it meets a non-pointer that is not a proto.Message it fails. Note the asymmetry: Marshal does a raw type assertion v.(proto.Message) and would panic instead, so this error is specifically the Unmarshal path's graceful rejection.

Source

Thrown at encoding/proto/proto.go:46

func (codec) Unmarshal(data []byte, v any) error {
	pm, err := getProtoMessage(v)
	if err != nil {
		return err
	}
	return proto.Unmarshal(data, pm)
}

func (codec) Name() string {
	return Name
}

func getProtoMessage(v any) (proto.Message, error) {
	if msg, ok := v.(proto.Message); ok {
		return msg, nil
	}
	val := reflect.ValueOf(v)
	if val.Kind() != reflect.Pointer {
		return nil, errors.New("not proto message")
	}

	val = val.Elem()
	return getProtoMessage(val.Interface())
}

View on GitHub (pinned to 668db92c2c)

Solutions

  1. Pass a pointer to a protoc-generated message (new google.golang.org/protobuf API) as the Unmarshal target, e.g. &pb.HelloReply{}
  2. If the payload is JSON, switch the codec/content-type to json instead of proto
  3. Ensure generated code comes from protoc-gen-go matching the protobuf runtime the codec registers (no gogo/legacy types)
  4. In generic code, type-switch and only route proto.Message targets through this codec

Example fix

// before
codec.Unmarshal(data, &map[string]any{}) // not proto message
// or handler with non-proto reply under proto content-type

// after
var reply pb.HelloReply
if err := codec.Unmarshal(data, &reply); err != nil { return err }
Defensive patterns

Strategy: type-guard

Type guard

func isProtoMessage(v any) bool {
    _, ok := v.(proto.Message)
    if ok { return true }
    rv := reflect.ValueOf(v)
    for rv.Kind() == reflect.Pointer && !rv.IsNil() {
        rv = rv.Elem()
        if _, ok := rv.Interface().(proto.Message); ok { return true }
    }
    return false
}

Try / catch

if err := codec.Unmarshal(data, target); err != nil {
    if err.Error() == "not proto message" {
        return fmt.Errorf("route negotiated proto codec but target %T is not a proto.Message", target)
    }
    return err
}

Prevention

When it happens

Trigger: The proto codec is selected (gRPC, or HTTP with Content-Type whose subtype is 'proto') and Unmarshal is handed a non-proto target: a map[string]any, *struct{}, a value type rather than pointer, a string/[]byte wrapper, or a custom struct from another codebase. Typical with reflect-based generic wrappers that allocate reply types dynamically.

Common situations: Server handler bound to a non-proto request/response struct while the route negotiated the proto codec; client declared with proto codec calling an endpoint whose reply is plain JSON-shaped; proxy/middleware that wraps messages in envelope types; protoc version skew producing types registered under google.golang.org/protobuf vs gogo/goprotobuf (the latter does not satisfy the new-gen proto.Message).

Related errors


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