larksuite/cli · error

expected hello_ack, got %T

Error message

expected hello_ack, got %T

What it means

doHello decoded the first frame from the server successfully, but it was not a *protocol.HelloAck. The library throws this because the handshake contract requires the immediate response to a Hello to be a HelloAck; any other message type means the peer is misbehaving or speaking a different protocol, so the connection is rejected with this type-mismatch error.

Source

Thrown at internal/event/consume/handshake.go:43

	}

	if err := conn.SetReadDeadline(time.Now().Add(helloAckTimeout)); err != nil {
		return nil, nil, fmt.Errorf("set hello_ack deadline: %w", err)
	}
	br := bufio.NewReader(conn)
	line, err := protocol.ReadFrame(br)
	if err != nil {
		return nil, nil, fmt.Errorf("no hello_ack received: %w", err)
	}
	// best-effort clear; if the conn is already broken, the loop's first read will surface it
	_ = conn.SetReadDeadline(time.Time{})
	msg, err := protocol.Decode(bytes.TrimRight(line, "\n"))
	if err != nil {
		return nil, nil, err
	}
	ack, ok := msg.(*protocol.HelloAck)
	if !ok {
		return nil, nil, fmt.Errorf("expected hello_ack, got %T", msg)
	}
	return ack, br, nil
}

View on GitHub (pinned to 7fd6ef3c07)

Solutions

  1. Align client and server protocol versions — upgrade the client (or server) so the ack type matches protocol.HelloAck.
  2. Log the decoded message content (%T plus payload) server-side to see what frame is actually being sent first.
  3. Confirm the endpoint is the event bus itself, not a proxy/banner-emitting service on the same port.
  4. If the server intentionally sends an error frame on rejection, surface that frame's payload to the caller instead of just the Go type.

Example fix

// before: only exact HelloAck accepted
ack, ok := msg.(*protocol.HelloAck)
if !ok { return nil, nil, fmt.Errorf("expected hello_ack, got %T", msg) }
// after: also recognize a typed rejection frame for a clearer failure
switch m := msg.(type) {
case *protocol.HelloAck:
    ack = m
case *protocol.Error:
    return nil, nil, fmt.Errorf("handshake rejected: %s", m.Message)
default:
    return nil, nil, fmt.Errorf("expected hello_ack, got %T", msg)
}
Defensive patterns

Strategy: type-guard

Type guard

func isHelloAck(msg protocol.Message) (*protocol.HelloAck, bool) {
    ack, ok := msg.(*protocol.HelloAck)
    return ack, ok
}

Try / catch

ack, br, err := doHello(conn, key, types, subID)
if err != nil {
    if strings.Contains(err.Error(), "expected hello_ack, got ") {
        log.Errorf("peer spoke unexpected first frame: %v — check protocol versions/endpoint", err)
    }
    conn.Close()
    return err
}

Prevention

When it happens

Trigger: protocol.Decode succeeds but the resulting message fails the *protocol.HelloAck type assertion in doHello (internal/event/consume/handshake.go:41-44) — e.g. the server sent an error frame, a Hello (server-initiated), a ping/pong, or a differently-versioned ack struct that decodes to another concrete type.

Common situations: Client and server protocol versions are out of sync (server replies with a new message type the old client decodes as something else); connected to the wrong service that sends a greeting/banner first; server pushes an error/reject message instead of the ack; middleware (auth proxy) injects its own first frame.

Related errors


AI-assisted analysis of larksuite/cli@7fd6ef3c07 (2026-09-04). Data as JSON: /api/errors/2668213149669e0a. Report an issue: GitHub.