larksuite/cli · error

no hello_ack received: %w

Error message

no hello_ack received: %w

What it means

After installing the 5s read deadline, doHello reads the hello_ack frame with protocol.ReadFrame; this error wraps any read failure — most commonly the 5-second helloAckTimeout expiring (i/o timeout) or the connection closing before any ack arrived. The library throws it because a Hello without a HelloAck means the server did not accept the subscription handshake, so the event stream cannot start.

Source

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

)

const helloAckTimeout = 5 * time.Second // symmetric with bus-side hello read deadline

// doHello returns a bufio.Reader holding any bytes already pulled off conn so events
// buffered with the ack in one TCP segment aren't dropped.
func doHello(conn net.Conn, eventKey string, eventTypes []string, subscriptionID string) (*protocol.HelloAck, *bufio.Reader, error) {
	hello := protocol.NewHello(os.Getpid(), eventKey, eventTypes, "v1", subscriptionID)
	if err := protocol.EncodeWithDeadline(conn, hello, protocol.WriteTimeout); err != nil {
		return nil, nil, err
	}

	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. Retry with backoff: Run's reconnect loop should redial — a timeout is often transient (server restart or load).
  2. Verify the dial address points at the correct event-bus host/port that speaks this frame protocol.
  3. Check server health/logs at the timestamp to see whether the Hello arrived and why no ack was emitted.
  4. Inspect protocol compatibility: client and server must agree on frame encoding and hello_ack shape (protocol version 'v1').
  5. If timeouts are frequent, investigate network latency/keepalive between client and bus.

Example fix

// before: single attempt, hard fail
ack, br, err := doHello(conn, eventKey, eventTypes, subID)
if err != nil { return err }
// after: let Run's reconnect loop retry transient handshake failures
if err := backoff.Retry(ctx, func() error {
    _, err := runOnce(ctx)
    return err
}); err != nil { return err }
Defensive patterns

Strategy: retry

Validate before calling

conn, err := net.DialTimeout("tcp", addr, 3*time.Second)
if err != nil {
    return fmt.Errorf("event bus unreachable at %s: %w", addr, err)
}

Type guard

func isHelloAckTimeout(err error) bool {
    var ne net.Error
    return errors.As(err, &ne) && ne.Timeout()
}

Try / catch

if _, _, err := doHello(conn, key, types, subID); err != nil {
    if isHelloAckTimeout(err) {
        conn.Close()
        return backoffRetry(ctx) // transient: server slow or restarting
    }
    return err // non-timeout read failure: surface to caller
}

Prevention

When it happens

Trigger: protocol.ReadFrame(br) returns an error after a Hello frame was sent on the conn: server never replied within helloAckTimeout (5s), server closed the TCP connection, or sent a malformed/oversized frame that the framer rejects.

Common situations: Event bus is down or overloaded and not answering handshakes; wrong endpoint/port (connected to something that is not the bus); server rejected the connection silently after Hello; network partition or TLS/proxy stripping the stream; server version speaks a different frame protocol.

Related errors


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