larksuite/cli · error
set hello_ack deadline: %w
Error message
set hello_ack deadline: %w
What it means
doHello wraps the error from conn.SetReadDeadline when it fails to install a 5-second read deadline (helloAckTimeout) on the event connection before waiting for the hello_ack frame. This means the underlying connection object refused or could not apply the deadline — the TCP connection is in a broken state and the handshake cannot proceed safely. doHello propagates it (wrapped with %w) to Run, which aborts the consume loop.
Source
Thrown at internal/event/consume/handshake.go:28
"net"
"os"
"time"
"github.com/larksuite/cli/internal/event/adapter/localbus/protocol"
)
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
- Reconnect: tear down the connection and retry the Run handshake loop — the connection is unusable, the deadline cannot be set.
- Check server-side logs around the connection time to see why the socket was reset or closed immediately after the Hello frame.
- If a custom/wrapped conn is passed in, ensure it embeds net.Conn (or *net.TCPConn) so SetReadDeadline works.
- Verify network path (proxy, firewall, keepalive) is not killing fresh connections between dial and handshake.
Example fix
// before: reusing a cached conn that may already be half-closed
conn := cachedConn
ack, _, err := doHello(conn, ...)
// after: dial fresh per handshake attempt
conn, err := dialer.DialContext(ctx, "tcp", addr)
if err != nil { return err }
ack, br, err := doHello(conn, eventKey, eventTypes, subscriptionID) Defensive patterns
Strategy: retry
Validate before calling
if tc, ok := conn.(*net.TCPConn); !ok {
return fmt.Errorf("conn %T does not support deadlines", conn)
} Type guard
func supportsDeadline(conn net.Conn) bool {
type deadlineSetter interface{ SetReadDeadline(time.Time) error }
_, ok := conn.(deadlineSetter)
return ok
} Try / catch
ack, br, err := doHello(conn, eventKey, eventTypes, subID)
if err != nil {
var netErr net.Error
if errors.As(err, &netErr) {
conn.Close()
return retryWithBackoff(ctx) // redial; conn is unusable
}
return err
} Prevention
- Always redial a fresh connection per handshake attempt instead of reusing cached conns.
- Ensure custom transports wrap net.Conn so SetReadDeadline is available.
- Monitor server-side connection churn; frequent resets right after Hello indicate server instability.
When it happens
Trigger: conn.SetReadDeadline(time.Now().Add(helloAckTimeout)) returns a non-nil error inside doHello (internal/event/consume/handshake.go:27-29), typically because the net.Conn is already closed, the peer reset the connection immediately after the Hello frame was written, or a non-deadline-capable conn was injected (e.g. in tests or a wrapped transport).
Common situations: The event bus server closed/reset the TCP connection right after accepting the Hello (crash, restart, idle timeout, firewall drop); the client used a stale pooled connection; a custom dialer or test fake returns a conn whose SetReadDeadline is unsupported or already errored.
Related errors
- no hello_ack received: %w
- annotated_csv did not parse into the rows the server reporte
- lark-cli timed out after {timeout}s
- failed to parse user info: %w
- app registration failed: read body: %w
AI-assisted analysis of larksuite/cli@7fd6ef3c07 (2026-09-04).
Data as JSON: /api/errors/e678b40af29a3241.
Report an issue: GitHub.