gastownhall/beads · error

identity: dial control listener

Error message

identity: dial control listener: %w

What it means

Identify dials the dbproxy control TCP listener to authenticate and fetch the proxy's identity. This error wraps net.DialTimeout's failure — the TCP connection to host:controlPort could not be established.

Solutions

  1. Verify the proxy control listener is running and note its actual control port
  2. Confirm host and controlPort match the proxy's startup logs/config
  3. Test reachability with nc -vz host controlPort
  4. Check firewall/security-group rules for the control port

Example fix

// before
reply, err := Identify("localhost", 42881, secret, 2*time.Second) // port guess
// after
reply, err := Identify("localhost", proxy.ControlPort(), secret, 5*time.Second)
Defensive patterns

Strategy: retry

Validate before calling

conn, err := net.DialTimeout("tcp", net.JoinHostPort(host, strconv.Itoa(controlPort)), timeout)
if err != nil { /* surface actionable message before calling Identify */ }

Try / catch

reply, err := Identify(host, port, secret, timeout)
if errors.Is(err, syscall.ECONNREFUSED) || strings.Contains(err.Error(), "dial control listener") {
	// wait for the proxy to start and retry with backoff
}

Prevention

When it happens

Trigger: Calling identity.Identify(host, controlPort, secret, timeout) when the control listener is not running, the port is wrong, the host is unreachable, or a firewall blocks the connection.

Common situations: Proxy daemon not started yet; stale/wrong control port from config; connecting from another container/network namespace; DNS resolving to the wrong interface; firewall dropping the port.

Understand the failure class

Background: ECONNREFUSED and "connection refused" / "could not connect to server" errors: what they mean and how to fix them — this error's family across 44 libraries.

Related errors


AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30). Data as JSON: /api/errors/4a285b052f6d8992. Report an issue: GitHub.

Appendix: source

Thrown at internal/storage/dbproxy/identity/control.go:45

// IdentReply is the authenticated identity published by a managed proxy.
type IdentReply struct {
	Schema      int    `json:"schema"`
	Role        string `json:"role"`
	RootID      string `json:"root_id"`
	UpstreamID  string `json:"upstream_id"`
	PID         int    `json:"pid"`
	Birth       string `json:"birth"`
	DataPort    int    `json:"data_port"`
	ControlPort int    `json:"control_port"`
	MAC         string `json:"mac"`
}

// Identify authenticates to a proxy control listener and returns its identity.
func Identify(host string, controlPort int, secret string, timeout time.Duration) (*IdentReply, error) {
	addr := net.JoinHostPort(host, strconv.Itoa(controlPort))
	conn, err := net.DialTimeout("tcp", addr, timeout)
	if err != nil {
		return nil, fmt.Errorf("identity: dial control listener: %w", err)
	}
	defer func() { _ = conn.Close() }()

	if err := conn.SetDeadline(time.Now().Add(timeout)); err != nil {
		return nil, fmt.Errorf("identity: set control deadline: %w", err)
	}
	nonceBytes := make([]byte, identNonceBytes)
	if _, err := rand.Read(nonceBytes); err != nil {
		return nil, fmt.Errorf("identity: generate request nonce: %w", err)
	}
	nonce := hex.EncodeToString(nonceBytes)
	if _, err := io.WriteString(conn, "IDENT "+secret+" "+nonce+"\n"); err != nil {
		return nil, fmt.Errorf("identity: write request: %w", err)
	}

	line, err := bufio.NewReader(io.LimitReader(conn, maxIdentReplyBytes+1)).ReadString('\n')
	if errors.Is(err, io.EOF) && len(line) == 0 {
		return nil, ErrIdentRefused

View on GitHub (pinned to 71377f2769)