t8y2/dbx · error

decode ZooKeeper session ID: %w

Error message

decode ZooKeeper session ID: %w

What it means

The connect response carries the 64-bit session ID; if decoder.int64() fails (not enough bytes left in the frame), newProtocolZooKeeperClient returns this error. A successful decode of 0 leads to zk.ErrSessionExpired instead, so this error strictly means the bytes could not be read, not an invalid session.

Source

Thrown at agents/drivers/argo-go/zookeeper_protocol.go:171

	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 {
	milliseconds := timeout.Milliseconds()
	if milliseconds < 1 {
		return 1
	}
	if milliseconds > math.MaxInt32 {
		return math.MaxInt32
	}

View on GitHub (pinned to c0390bff16)

Solutions

  1. Log/inspect the raw response frame and verify field sizes: int32 version, int32 timeout, int64 sessionID, bytes password
  2. Fix any intermediary (proxy/NAT) that fragments or truncates the stream
  3. Correct the encoder in test doubles to write the session ID as a full 8-byte int64
  4. Upgrade/align ZooKeeper client and server versions to the same wire protocol

Example fix

// before (test fake)
buf.int32(0) // wrong: wrote sessionID as int32
// after
buf.int32(0)
buf.int32(30000)
buf.int64(0x1234) // full 64-bit session ID
buf.bytes(passwd)
Defensive patterns

Strategy: type-guard

Validate before calling

func frameHasSessionID(decoder *zooKeeperDecoder) bool {
    return decoder.remaining() >= 8 // sessionID int64 after version+timeout
}

Try / catch

client, err := newProtocolZooKeeperClient(conn, timeout)
if err != nil {
    var expired = (err == zk.ErrSessionExpired) // note: sessionID==0 maps here, not this decode error
    if !expired && strings.Contains(err.Error(), "decode ZooKeeper session ID") {
        log.Error("connect response missing/truncated session ID")
    }
    return err
}

Prevention

When it happens

Trigger: newProtocolZooKeeperClient decoding a connect response shorter than the layout through the session-ID field (~12 bytes missing/misplaced) — truncated or mis-framed server data.

Common situations: Proxy or firewall clipping large frames; test fakes with wrong byte counts (int32 vs int64 confusion); server sending an error payload instead of the normal ConnectResponse structure.

Related errors


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