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/main/java/org/elasticsearch/plugins/PluginDescriptor.java
out.writeOptionalString(moduleName);
        }
        out.writeStringCollection(extendedPlugins);
        out.writeBoolean(hasNativeController);
        if (out.getTransportVersion().onOrAfter(LICENSED_PLUGINS_SUPPORT)) {
            if (out.getTransportVersion().before(BOOTSTRAP_SUPPORT_REMOVED)) {
                out.writeString("ISOLATED");
                out.writeOptionalString(null);
            }
            out.writeBoolean(isLicensed);
        }
        if (out.getTransportVersion().onOrAfter(TransportVersions.V_8_4_0)) {
            out.writeBoolean(isModular);
            out.writeBoolean(isStable);
        }
    }
    /**
     * Reads the descriptor file for a plugin.
     *
     * @param pluginDir the path to the root directory for the plugin
     * @return the plugin info
     * @throws IOException if an I/O exception occurred reading the plugin descriptor
     */
    public static PluginDescriptor readFromProperties(final Path pluginDir) throws IOException {
        Path internalDescriptorFile = pluginDir.resolve(INTERNAL_DESCRIPTOR_FILENAME);
        Path stableDescriptorFile = pluginDir.resolve(STABLE_DESCRIPTOR_FILENAME);
        final BiFunction<Map<String, String>, String, PluginDescriptor> reader;
        final Path descriptorFile;
        boolean internalDescriptorExists = Files.exists(internalDescriptorFile);
        boolean stableDescriptorExists = Files.exists(stableDescriptorFile);