AlexxIT/go2rtc · error
hap: can't dial witout client_id or client_private
Error message
hap: can't dial witout client_id or client_private
What it means
Client.Dial refuses to start HAP session establishment when the client's pairing identity is not configured: either ClientID or ClientPrivate (the client's ed25519 ID and private key from a previous pairing) is empty. Dial needs these to sign the session and authenticate to the accessory.
Solutions
- Set ClientID and ClientPrivate before calling Dial — load them from persisted pairing state or pair the device first.
- Check the config file/struct mapping: the JSON keys client_id and client_private must actually populate the fields.
- If no pairing exists yet, run the Pair flow first to establish the client identity, then persist it.
- Log the lengths of both fields before Dial to confirm they are non-empty at runtime.
Example fix
// before: client identity missing -> Dial fails
client := &hap.Client{DeviceID: deviceID, DeviceAddress: addr}
err := client.Dial()
// after: load persisted identity first
client := &hap.Client{
DeviceID: deviceID, DeviceAddress: addr,
ClientID: cfg.HAP.ClientID, // non-empty
ClientPrivate: cfg.HAP.ClientPrivate, // non-empty ed25519 private key
}
if len(client.ClientID) == 0 || len(client.ClientPrivate) == 0 {
return errors.New("pair the device first; client identity missing")
}
err := client.Dial() Defensive patterns
Strategy: validation
Validate before calling
if len(client.ClientID) == 0 || len(client.ClientPrivate) == 0 {
return errors.New("client identity missing: pair the device first and persist client_id/client_private")
} Try / catch
if err := client.Dial(); err != nil {
if strings.Contains(err.Error(), "witout client_id") {
return fmt.Errorf("configuration error: run the pairing setup to create the client identity: %w", err)
}
return err
} Prevention
- Persist ClientID/ClientPrivate immediately after the first successful Pair and reload them at startup.
- Validate the pairing config struct (both fields non-empty) before constructing the Client.
- Watch for JSON key mismatches (client_id vs ClientID) that silently leave fields empty.
When it happens
Trigger: Constructing a Client with only device fields (DeviceID/DeviceAddress) and forgetting to load the client identity; loading persisted pairing state from a config file that is missing the client_id/client_private fields; creating a fresh Client for a device that was already paired elsewhere.
Common situations: Config file missing the client identity keys after a fresh install or migration; JSON unmarshalling silently leaving the fields empty due to wrong key names; running the client on a new host without copying the pairing credentials.
Related errors
- credentials: storage not initialized
- hap: ValidateSignature
- hap: VerifyServerAuthenticator
- hap: ValidateSignature
- hap: no free streams
AI-assisted analysis of AlexxIT/go2rtc@c245815e75 (2026-09-07).
Data as JSON: /api/errors/555fb5e3e2af167d.
Report an issue: GitHub.
Appendix: source
Thrown at pkg/hap/client.go:90
}
func (c *Client) URL() string {
return fmt.Sprintf(
"homekit://%s?device_id=%s&device_public=%16x&client_id=%s&client_private=%32x",
c.DeviceAddress, c.DeviceID, c.DevicePublic, c.ClientID, c.ClientPrivate,
)
}
func (c *Client) DeviceHost() string {
if i := strings.IndexByte(c.DeviceAddress, ':'); i > 0 {
return c.DeviceAddress[:i]
}
return c.DeviceAddress
}
func (c *Client) Dial() (err error) {
if len(c.ClientID) == 0 || len(c.ClientPrivate) == 0 {
return errors.New("hap: can't dial witout client_id or client_private")
}
// update device address (host and/or port) before dial
_ = mdns.QueryOrDiscovery(c.DeviceHost(), mdns.ServiceHAP, func(entry *mdns.ServiceEntry) bool {
if entry.Complete() && entry.Info["id"] == c.DeviceID {
c.DeviceAddress = entry.Addr()
return true
}
return false
})
// TODO: close conn on error
if c.Conn, err = net.DialTimeout("tcp", c.DeviceAddress, ConnDialTimeout); err != nil {
return
}
c.reader = bufio.NewReader(c.Conn)
View on GitHub (pinned to c245815e75)