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
- Align client and server protocol versions — upgrade the client (or server) so the ack type matches protocol.HelloAck.
- Log the decoded message content (%T plus payload) server-side to see what frame is actually being sent first.
- Confirm the endpoint is the event bus itself, not a proxy/banner-emitting service on the same port.
- 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
- Pin and verify protocol version ('v1') on both client and server before deploy.
- Never point the consumer at a non-bus endpoint that emits a greeting frame.
- Add integration tests that assert the server's first reply decodes to *protocol.HelloAck.
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
- protocol: frame exceeds MaxFrameBytes
- app registration failed: response missing device_code
- app registration returned credentials with a contradictory t
- exec provider value for id %q is not a string
- file provider JSON Pointer %q resolved to non-string value
AI-assisted analysis of larksuite/cli@7fd6ef3c07 (2026-09-04).
Data as JSON: /api/errors/2668213149669e0a.
Report an issue: GitHub.