We pass `null` for the class name here:

https://github.com/elastic/elasticsearch/blob/f086dd18ddc705bf84a1ff99d35196ec2498fd52/server/src/main/java/org/elasticsearch/plugins/PluginDescriptor.java#L261

However the class name is mandatory for the transport protocol today:

https://github.com/elastic/elasticsearch/blob/f086dd18ddc705bf84a1ff99d35196ec2498fd52/server/src/main/java/org/elasticsearch/plugins/PluginDescriptor.java#L156

This prevents various APIs from working in a cluster with stable plugins installed.
server/src/test/java/org/elasticsearch/plugins/PluginDescriptorTests.java
"true"
    );
    private interface PropertiesWriter {
        void write(Path path, String... props) throws IOException;
    }
    private interface DescriptorWriter {
        PluginDescriptor write(String... props) throws IOException;
    }
    static PluginDescriptor mockInternalDescriptor(String... additionalProps) throws IOException {
        return mockDescriptor(INTERNAL_DESCRIPTOR_TEMPLATE, PluginTestUtil::writePluginProperties, additionalProps);
    }
    static PluginDescriptor mockStableDescriptor(String... additionalProps) throws IOException {
        return mockDescriptor(STABLE_DESCRIPTOR_TEMPLATE, PluginTestUtil::writeStablePluginProperties, additionalProps);
    }
    static PluginDescriptor mockDescriptor(Map<String, String> template, PropertiesWriter writer, String... addProps) throws IOException {
        assert addProps.length % 2 == 0;
        Map<String, String> propsMap = new HashMap<>(template);
        for (int i = 0; i < addProps.length; i += 2) {
            propsMap.put(addProps[i], addProps[i + 1]);
        }
        String[] props = new String[propsMap.size() * 2];
        int i = 0;
        for (var e : propsMap.entrySet()) {
            props[i] = e.getKey();
            props[i + 1] = e.getValue();
            i += 2;
        }
        Path pluginDir = createTempDir().resolve("fake-plugin");
        writer.write(pluginDir, props);
        return PluginDescriptor.readFromProperties(pluginDir);