#3742·sarama

DescribeCluster() fails with io.EOF against brokers < 2.8 after v1.47.0 due to missing API key check in restrictApiVersion

Author: wxldonnaBJCreated Sep 7, 2026Updated Sep 7, 2026
Description

After upgrading from v1.60.0 to v1.60.2, ClusterAdmin.DescribeCluster() fails with io.EOF when the Kafka broker is older than 2.8. The customer's broker log shows:

[2026-09-04 06:49:43,649] ERROR Closing socket for 10.12.36.50:9094-10.47.194.93:49678-7813801 because of error (kafka.network.Processor) org.apache.kafka.common.errors.InvalidRequestException: Unknown API key 60 Users on NewConfig() with no explicit Version set are affected.

Root Cause The issue has two parts that combine to produce the failure.

Part 1 — The DefaultVersion bump was the trigger v1.60.0 had DefaultVersion = V2_6_0_0. v1.60.2 bumped it to V2_8_0_0. DescribeCluster() in admin.go has a version gate:

go
if ca.conf.Version.IsAtLeast(V2_8_0_0) {
    brokers, controllerID, err = ca.describeClusterUsingAPI() // sends API key 60
    ...
}
return ca.describeClusterUsingMetadata() // Metadata API fallback
Version DefaultVersion IsAtLeast(V2_8_0_0) API key 60 sent Result
v1.60.0 V2_6_0_0 false No Works — Metadata API used
v1.60.2 V2_8_0_0 true Yes Fails — broker < 2.8 closes socket

The code for describeClusterUsingAPI() existed in v1.60.0 too — it was dormant because the gate was never reached. The DefaultVersion bump is what activated it.

Part 2 — The fallback is fragile DescribeCluster() does have a fallback to the Metadata API, but it only triggers on ErrUnsupportedVersion (Kafka error code 35):

go
if ca.conf.Version.IsAtLeast(V2_8_0_0) {
    brokers, controllerID, err = ca.describeClusterUsingAPI()
    if err == nil {
        return brokers, controllerID, nil
    }
    if !errors.Is(err, ErrUnsupportedVersion) { // ← only catches error code 35
        return nil, 0, err                       // ← io.EOF falls through here
    }
}
return ca.describeClusterUsingMetadata()

A broker older than 2.8 does not respond with error code 35 when it receives an unknown API key. It closes the TCP connection entirely. Sarama receives io.EOF. The fallback condition does not match, so io.EOF is returned to the caller instead of gracefully falling back to the Metadata API.

Part 3 — The underlying gap: restrictApiVersion() ignores absent keys

The real fix should happen one layer deeper. Sarama already sends ApiVersionsRequest to every broker at connection time (default: enabled) and stores the result in brokerAPIVersions. A broker < 2.8 will not include API key 60 in its response — it already told Sarama the key is unsupported.

But restrictApiVersion() in api_versions.go does nothing with this information when a key is absent:

go
func restrictApiVersion(pb protocolBody, brokerVersions apiVersionMap) error {
    key := pb.key()
    clientMax := pb.version()

    if brokerVersionRange := brokerVersions[key]; brokerVersionRange != nil {
        pb.setVersion(min(clientMax, max(min(clientMax, brokerVersionRange.maxVersion), brokerVersionRange.minVersion)))
        return nil
    }

    return nil // ← key absent treated same as "no data at all" — request sent anyway
}

Proposed Fix One change in api_versions.go:

go
func restrictApiVersion(pb protocolBody, brokerVersions apiVersionMap) error {
    key := pb.key()
    clientMax := pb.version()

    if brokerVersionRange := brokerVersions[key]; brokerVersionRange != nil {
        pb.setVersion(min(clientMax, max(min(clientMax, brokerVersionRange.maxVersion), brokerVersionRange.minVersion)))
        return nil
    }

    // If brokerAPIVersions is populated (ApiVersionsRequest succeeded) but
    // this key is absent, the broker does not support this API at all.
    // Return ErrUnsupportedVersion so callers with fallback logic (e.g.
    // DescribeCluster) can degrade gracefully instead of sending a request
    // the broker will reject by closing the connection.
    if len(brokerVersions) > 0 {
        return ErrUnsupportedVersion
    }

    return nil // ApiVersionsRequest disabled or not yet completed — no restriction
}

On connect, Sarama fetches ApiVersionsResponse from broker (already happens by default) Before sending DescribeCluster (key 60), restrictApiVersion sees key 60 is absent → returns ErrUnsupportedVersion — the request never reaches the broker The existing fallback in DescribeCluster() catches ErrUnsupportedVersion → uses Metadata API → works correctly Users with NewConfig() work against any broker version without any config change