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

  1. Verify TLS settings match the server: use the TLS dial path only for a TLS-enabled listener and plaintext otherwise
  2. Confirm the endpoint is a real ZooKeeper client port (2181/2281) and not an admin/HTTP port
  3. Capture the raw response frame to identify what the server actually sent
  4. 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

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


AI-assisted analysis of t8y2/dbx@c0390bff16 (2026-09-05). Data as JSON: /api/errors/6409b1e9a8390440. Report an issue: GitHub.