t8y2/dbx · error
decode ZooKeeper protocol version: %w
Error message
decode ZooKeeper protocol version: %w
What it means
The connect response must decode as an int32 protocol version; if newZooKeeperDecoder.int32() cannot read 4 bytes from the response frame, this error is returned. It means the server sent a frame too short or not matching the ZooKeeper ConnectResponse layout — a protocol mismatch rather than a network failure.
Source
Thrown at agents/drivers/argo-go/zookeeper_protocol.go:164
timeout = defaultConnectTimeout
}
client := &protocolZooKeeperClient{connection: connection, timeout: timeout}
request := &zooKeeperEncoder{}
request.int32(zooKeeperProtocolVersion)
request.int64(0)
request.int32(zooKeeperTimeoutMillis(timeout))
request.int64(0)
request.bytes(make([]byte, 16))
if err := client.writeFrame(request.data()); err != nil {
return nil, fmt.Errorf("send ZooKeeper connect request: %w", err)
}
response, err := client.readFrame()
if err != nil {
return nil, fmt.Errorf("read ZooKeeper connect response: %w", err)
}
decoder := newZooKeeperDecoder(response)
if _, err := decoder.int32(); err != nil {
return nil, fmt.Errorf("decode ZooKeeper protocol version: %w", err)
}
if _, err := decoder.int32(); err != nil {
return nil, fmt.Errorf("decode ZooKeeper session timeout: %w", err)
}
sessionID, err := decoder.int64()
if err != nil {
return nil, fmt.Errorf("decode ZooKeeper session ID: %w", err)
}
if _, err := decoder.bytes(); err != nil {
return nil, fmt.Errorf("decode ZooKeeper session password: %w", err)
}
if sessionID == 0 {
return nil, zk.ErrSessionExpired
}
return client, nil
}
func zooKeeperTimeoutMillis(timeout time.Duration) int32 {View on GitHub (pinned to c0390bff16)
Solutions
- Verify TLS settings match the server: use the TLS dial path only for a TLS-enabled listener and plaintext otherwise
- Confirm the endpoint is a real ZooKeeper client port (2181/2281) and not an admin/HTTP port
- Capture the raw response frame to identify what the server actually sent
- Check ZooKeeper client/server version compatibility for the connect response layout
Example fix
// before addr := "zk1.example.com:2181" // server actually requires TLS on 2281 conn := connectTLS(addr) // after addr := "zk1.example.com:2281" // TLS client port conn := connectTLS(addr)
Defensive patterns
Strategy: validation
Validate before calling
func looksLikeZKConnectResponse(frame []byte) bool {
return len(frame) >= 4 // protocol version int32 is the first field
} Type guard
func hasMinConnectResponse(frame []byte) bool { return len(frame) >= 4 } Try / catch
client, err := newProtocolZooKeeperClient(conn, timeout)
if err != nil {
if strings.Contains(err.Error(), "decode ZooKeeper protocol version") {
return fmt.Errorf("endpoint %s does not speak the ZooKeeper protocol; check TLS vs plaintext port", address)
}
return err
} Prevention
- Match TLS client settings to TLS-enabled listener ports (2181 plaintext vs 2281 TLS)
- Smoke-test the raw protocol against each endpoint during deploys
- Check version compatibility between client library and ZooKeeper server
- Bypass suspicious proxies when diagnosing decode failures
When it happens
Trigger: newProtocolZooKeeperClient reading a response frame whose payload has fewer than 4 bytes, or bytes produced by a non-ZooKeeper protocol (e.g. connecting to an HTTP endpoint or a TLS listener in plaintext, or vice versa), making the first int32 decode fail.
Common situations: Port mixups (TLS client to a plaintext port or the reverse); ZooKeeper version speaking an unexpected wire format through a broker; a proxy returning an error page/banner that got framed as the response; unit-test fakes writing malformed responses.
Related errors
- decode ZooKeeper session timeout: %w
- decode ZooKeeper session ID: %w
- decode ZooKeeper session password: %w
- decode ZooKeeper SASL round %d: %w
- ZooKeeper sent an unexpected token after GSSAPI completion
AI-assisted analysis of t8y2/dbx@c0390bff16 (2026-09-05).
Data as JSON: /api/errors/6409b1e9a8390440.
Report an issue: GitHub.